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
45 changes: 6 additions & 39 deletions sample-template/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Use language that’s accessible to a broad audience.

- **Model**: (what logic/relations are implemented)
- **Runner**: (how to execute: Python script / CLI commands / notebook)
- **Runbook**: `runbook.md` — a paste-testable walkthrough that reproduces the template step by step with the RAI skills. The recommended way to learn how it is built; as important a reference as the script itself.
- **Sample data**: (what it represents)
- **Outputs**: (what results are produced and where)

Expand Down Expand Up @@ -114,59 +115,25 @@ Provide a short annotated tree. Keep it to the top level and the most important
└─ ...
```

**Start here**: point to the one command/script that runs end-to-end.
**Start here**: name the one command/script that runs end-to-end, and point to `runbook.md` to reproduce the template step by step with the RAI skills.

## Sample data

Describe what the sample data represents, and any important notes about its structure or contents.

## Model overview

Describe the main entities and the most important relationships.
Give the reader the shape of the model at a glance — the README is the map, not the schema. Keep this to the three intro bullets; the full concept-and-property definitions live in the script, so don't restate them here.

- **Key entities**: (e.g., `product`, `warehouse`, `lane`)
- **Key entities**: (e.g., `product`, `warehouse`, `lane`) — name the concepts and, in a phrase each, what they represent
- **Primary identifiers**: what uniquely identifies each entity
- **Important invariants**: (e.g., demand non-negative; capacity limits)

Document the data model concept-by-concept.

### Concepts (one table per concept)

For each key concept/type:

1. Write a brief sentence *outside the table* describing what the concept represents and how it’s used.
2. Add a table with **one row per property**.

Suggested table shape:

| Property | Type | Identifying? | Notes |
|---|---|---|---|
| `product_id` | int | Yes | Loaded from `data/products.csv` |
| `name` | string | No | Human-readable name |
| `category` | string | No | Used for grouping/filters |

Repeat this table for each concept (e.g., `product`, `warehouse`, `lane`).

### Relationships (only if there are non-property relations)

Only include a Relationships table if the model defines relations **beyond concept properties** (e.g., standalone predicates like `demand(product, date, units)` or recursive relations).

| Relationship | Schema (reading string fields) | Notes |
|---|---|---|
| `demand(product, date, units)` | `product`, `date`, `units` | Units are weekly; non-negative |
| `lane(source, destination, capacity)` | `source`, `destination`, `capacity` | Capacity is per day |
For the full concept and property definitions, read `<template>.py`; to see them built step by step with the RAI skills, follow `runbook.md`.

## How it works

Give a short, end-to-end walkthrough of the template, with relevant code examples. Example:

- Ingest sample CSVs into relations
- Derive intermediate relations (feature engineering / aggregations)
- Apply constraints/objective (if optimization)
- Compute outputs (recommended actions / assignments)
- Export results to CSV / print summary

If helpful, add a small diagram:
Give a short, plain-language walkthrough of the chain — what each stage reads and produces and why — so a reader understands the flow without opening the code. Keep code out of this section: point to the script for the implementation and to `runbook.md` for the skill-driven reproduction. One small flow diagram is welcome:

```text
CSV inputs → load → base relations → model logic → results → export
Expand Down
229 changes: 131 additions & 98 deletions v1/ad_spend_allocation/README.md

Large diffs are not rendered by default.

142 changes: 42 additions & 100 deletions v1/bom-reachability/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ sidebar:

## What this template is for

A bill of materials (BOM) defines how finished products are built from components and raw materials through multiple assembly stages. Understanding the full transitive dependency tree -- not just direct inputs -- is critical for supply chain risk management. This template demonstrates two graph analysis techniques on a BOM structure:
A bill of materials (BOM) defines how finished products are built from components and raw materials through multiple assembly stages. When a supplier slips, a plant goes down, or a part is recalled, the pressing question is not "what does this product use directly?" but "which finished goods ultimately depend on this part, and how far up the chain does the damage travel?" Direct inputs are easy to read off a spreadsheet; the full transitive dependency tree — and the shared components that quietly sit on everyone's critical path — are not.

1. **Reachability** (`reachable(full=True)`) -- Trace all transitive dependencies to answer "What does Product X ultimately depend on?" across multiple assembly tiers.
2. **Betweenness Centrality** -- Identify structural bottleneck components that sit on the most dependency paths between finished goods and raw materials.
This template turns a BOM into a dependency graph so you can trace those transitive dependencies, pinpoint the components that are structural bottlenecks across product lines, and enumerate the end-to-end assembly chains that build each finished good. **It runs on RelationalAI's graph reasoner, using reachability, betweenness centrality, and path enumeration directly over the ontology.**

## Who this is for

Expand All @@ -32,20 +31,21 @@ A bill of materials (BOM) defines how finished products are built from component

## What you'll build

- Load a 9-SKU, 14-BOM-entry product structure from CSV (consumer electronics: smartphones, tablets, components, raw materials)
- Construct a directed dependency graph where edges point from output SKU to input SKU ("depends on")
- Compute all-pairs reachability to map full transitive dependency trees
- List dependencies per finished good, broken down by type (COMPONENT vs RAW_MATERIAL)
- Rank components by how many other SKUs depend on them
- Compute betweenness centrality to identify structural bottlenecks
- Enumerate the end-to-end assembly chains that build each finished good and persist each one's assembly depth
- A directed dependency graph over the BOM (SKU nodes, output-to-input "depends on" edges) built with the graph reasoner's `Graph` construction.
- A full transitive dependency tree per finished good, broken down by type (component vs raw material), from all-pairs `reachable(full=True)`.
- A most-depended-on ranking of SKUs and a betweenness-centrality ranking that flags the structural bottleneck components.
- An enumeration of the end-to-end assembly chains that build each finished good, reduced to the maximal (non-extendable) chains via path enumeration.
- An `assembly_depth` property persisted back onto each SKU, so the longest chain terminating at each finished good is queryable as ontology after the run.

Built on RelationalAI's graph reasoner: **reachability** for transitive dependencies, **betweenness centrality** for bottlenecks, and **path enumeration** for assembly chains.

## What's included

- **Self-contained script**: `bom_reachability.py` -- Runs the full analysis end-to-end
- **Data**: `data/skus.csv` (9 SKUs across 3 tiers) and `data/bill_of_materials.csv` (14 BOM entries with site-specific assembly)
- **Model**: two concepts (`SKU` and `BillOfMaterials`) wired into a directed dependency graph, plus a derived `feeds` self-relationship on `SKU` and a persisted `assembly_depth` property.
- **Runner**: `bom_reachability.py` — a single self-contained Python script that runs reachability, betweenness centrality, and assembly-path enumeration end to end.
- **Runbook**: `runbook.md` — a paste-testable walkthrough that reproduces the template step by step with the RAI skills; as important a reference as the script itself.
- **Sample data**: `data/skus.csv` (9 SKUs across 3 tiers) and `data/bill_of_materials.csv` (14 BOM entries with site-specific assembly). See *Sample data* below.
- **Outputs**: printed transitive-dependency lists per finished good, a most-depended-on and betweenness ranking, the maximal assembly chains, and an `assembly_depth` property written back onto the SKU ontology.

## Prerequisites

Expand Down Expand Up @@ -125,121 +125,53 @@ Built on RelationalAI's graph reasoner: **reachability** for transitive dependen
└── bill_of_materials.csv # 14 site-specific BOM entries (output SKU requires input SKU)
```

**Start here**: run `python bom_reachability.py` for the full analysis end to end (reachability, betweenness centrality, and assembly-path enumeration), or follow `runbook.md` to rebuild it step by step with a coding agent.
**Start here**: run `python bom_reachability.py` for the full analysis end to end (reachability, betweenness centrality, and assembly-path enumeration), or follow `runbook.md` to reproduce it step by step with the RAI skills.

## Sample data

`data/skus.csv` holds 9 SKUs spanning the three assembly tiers: 4 `RAW_MATERIAL` (silicon wafer, display glass, lithium-ion cells, NAND flash), 3 `COMPONENT` (mobile processor, OLED display, battery pack), and 2 `FINISHED_GOOD` (a smartphone and a tablet). Each row carries an `ID`, `NAME`, `TYPE`, `CATEGORY`, `UNIT_OF_MEASURE`, `LEAD_TIME_DAYS`, `UNIT_COST`, and `UNIT_PRICE`; the graph analysis uses `ID`, `NAME`, `TYPE`, and `CATEGORY`.

`data/bill_of_materials.csv` holds 14 BOM entries, each linking an `OUTPUT_SKU_ID` (what is produced) to an `INPUT_SKU_ID` (what it requires), with `ID`, `SITE_ID`, and `INPUT_QUANTITY`. The same output-to-input pair is recorded once per `SITE_ID`, so multi-site assemblies appear as duplicate edges (for example, `SKU001` is assembled at both `S001` and `S012`). The graph deduplicates these automatically -- see the multi-edges note under Troubleshooting.
`data/bill_of_materials.csv` holds 14 BOM entries, each linking an `OUTPUT_SKU_ID` (what is produced) to an `INPUT_SKU_ID` (what it requires), with `ID`, `SITE_ID`, and `INPUT_QUANTITY`. The same output-to-input pair is recorded once per `SITE_ID`, so multi-site assemblies appear as duplicate edges (for example, `SKU001` is assembled at both `S001` and `S012`). The graph deduplicates these automatically see the multi-edges note under Troubleshooting.

## Model overview

- **Key entities**: `SKU` (one stock-keeping unit at any tier) and `BillOfMaterials` (one output-to-input link, used as the directed graph's edge concept).
- **Primary identifiers**: `SKU` by `id`; `BillOfMaterials` by `id`.
- **Important invariants**: each `BillOfMaterials` row points an output SKU at one input SKU ("depends on"); the BOM is acyclic (raw materials feed components feed finished goods, never back).

### `SKU`
The model also derives a `feeds` self-relationship on `SKU` (input SKU feeds output SKU), built from the `BillOfMaterials` intermediary and used to enumerate assembly paths, plus a persisted `assembly_depth` property (the longest assembly chain terminating at each SKU).

One stock-keeping unit at any assembly tier (raw material, component, or finished good).

| Property | Type | Identifying? | Notes |
|---|---|---|---|
| `id` | string | Yes | Loaded from `data/skus.csv` (`ID`) |
| `name` | string | No | Human-readable name (`NAME`) |
| `type` | string | No | `RAW_MATERIAL`, `COMPONENT`, or `FINISHED_GOOD` (`TYPE`) |
| `category` | string | No | Used for grouping/filters (`CATEGORY`) |
| `assembly_depth` | int | No | Derived and persisted: longest assembly chain terminating at this SKU (PREVIEW stage) |

The model also derives a `feeds` self-relationship on `SKU` (`SKU feeds into SKU`): a binary input-feeds-output edge built from the `BillOfMaterials` intermediary and used to enumerate assembly paths.

### `BillOfMaterials`

One link in the bill of materials, recorded per assembly site and used as the graph's edge concept.

| Property | Type | Identifying? | Notes |
|---|---|---|---|
| `id` | string | Yes | Loaded from `data/bill_of_materials.csv` (`ID`) |

| Relationship | Schema | Notes |
|---|---|---|
| `output_sku` | `BillOfMaterials produces SKU` | The SKU this entry produces; graph edge source |
| `input_sku` | `BillOfMaterials requires SKU` | The SKU this entry requires; graph edge destination |
For the full concept and property definitions, see `bom_reachability.py`; `runbook.md` builds them step by step with the RAI skills.

## How it works

```text
CSV files --> Define SKU + BOM concepts --> Build directed graph --> Reachability analysis --> Betweenness centrality --> Display results
```

### 1. Load Ontology

SKU and BillOfMaterials concepts are loaded from CSV. Each BOM entry links an output SKU (what is produced) to an input SKU (what is required):
The pipeline loads the ontology, builds a directed graph, then runs three graph analyses over it:

```python
SKU = model.Concept("SKU", identify_by={"id": String})
BillOfMaterials = model.Concept("BillOfMaterials", identify_by={"id": String})
BillOfMaterials.output_sku = model.Relationship(f"{BillOfMaterials} produces {SKU}")
BillOfMaterials.input_sku = model.Relationship(f"{BillOfMaterials} requires {SKU}")
```text
CSV files --> Define SKU + BOM concepts --> Build directed graph --> Reachability analysis --> Betweenness centrality --> Assembly-path enumeration --> Display results
```

### 2. Build Directed Graph
### 1. Load ontology

The graph uses BillOfMaterials as the edge concept, with edges pointing from output to input ("depends on"):
The `SKU` and `BillOfMaterials` concepts load from CSV. Each BOM entry links an output SKU (what is produced) to an input SKU (what is required), which becomes the directed "depends on" edge.

```python
graph = Graph(
model, directed=True, weighted=False,
node_concept=SKU,
edge_concept=BillOfMaterials,
edge_src_relationship=BillOfMaterials.output_sku,
edge_dst_relationship=BillOfMaterials.input_sku,
)
```
### 2. Build directed graph

### 3. Trace Dependencies
The graph uses `BillOfMaterials` as the edge concept, with edges pointing from output SKU to input SKU — so following an edge means following a dependency.

`reachable(full=True)` computes all-pairs reachability -- every (source, destination) pair where a directed path exists:
### 3. Trace dependencies

```python
reachable = graph.reachable(full=True)
All-pairs reachability computes every source-and-destination pair where a directed path exists, giving the full transitive dependency tree behind each finished good rather than just its direct inputs.

src, dst = graph.Node.ref("src"), graph.Node.ref("dst")
all_deps_df = where(reachable(src, dst)).select(
src.id.alias("product_id"),
dst.id.alias("dep_id"),
...
).to_df()
```
### 4. Identify bottlenecks

### 4. Identify Bottlenecks
Betweenness centrality ranks components by how many shortest dependency paths pass through them. Components with high betweenness are structural bottlenecks — disrupting them affects the most product lines.

Betweenness centrality ranks components by how many shortest dependency paths pass through them:
### 5. Enumerate assembly paths (PREVIEW, requires `relationalai>=1.15`)

```python
betweenness = graph.betweenness_centrality()
```
Where reachability returns dependency *pairs*, path enumeration returns the actual *build sequences*. It walks the derived `feeds` edge to enumerate every assembly path; because the BOM is acyclic, this yields exactly the simple paths with no cycle risk. A maximal-paths view keeps only the longest non-extendable chains, and the longest assembly depth is persisted as `SKU.assembly_depth`.

Components with high betweenness are structural bottlenecks -- disrupting them affects the most product lines.

### 5. Enumerate Assembly Paths (PREVIEW, requires `relationalai>=1.15`)

Where reachability returns dependency *pairs*, path enumeration returns the actual *build sequences*. It derives a SKU-to-SKU `feeds` edge from the `BillOfMaterials` intermediary (input SKU feeds output SKU) and enumerates every assembly path; because the BOM is acyclic, `.all_paths()` yields exactly the simple paths -- no cycle risk. A maximal-paths view keeps only the longest non-extendable chains, and the longest assembly depth is persisted as `SKU.assembly_depth`.

```python
SKU.feeds = model.Relationship(f"{SKU} feeds into {SKU}", short_name="feeds")
p = model.path(SKU.feeds.repeat(1, MAX_ASSEMBLY_HOPS)).all_paths()
assembly_df = (
model.where(p)
.select(
p.alias("path"),
p.nodes["index"].alias("step"),
SKU(p.nodes).id.alias("sku_id"),
SKU(p.nodes).name.alias("sku_name"),
)
.to_df()
)
```
See `bom_reachability.py` for the implementation and `runbook.md` for the skill-driven reproduction.

## Customize this template

Expand Down Expand Up @@ -271,23 +203,33 @@ Focus on the first changes most users will make.
<details>
<summary>Why do I see a "multi-edges" warning?</summary>

- The BOM data includes site-specific entries (e.g., SKU001 is assembled at both S001 and S012). This creates duplicate edges between the same SKU pair. The warning is informational -- the graph deduplicates automatically. To suppress it, add `aggregator="sum"` to the Graph constructor.
- The BOM data includes site-specific entries (e.g., SKU001 is assembled at both S001 and S012). This creates duplicate edges between the same SKU pair. The warning is informational the graph deduplicates automatically. To suppress it, add `aggregator="sum"` to the Graph constructor.

</details>

<details>
<summary>Why does authentication/configuration fail?</summary>

- Run `rai init` to create/update `raiconfig.toml`.
- Run `rai init` to create/update `raiconfig.yaml`.
- If you have multiple profiles, set `RAI_PROFILE` or switch profiles in your config.

</details>

## Learn more

### Core concepts

- [RelationalAI documentation](https://docs.relational.ai/) — language, modeling, and reasoner reference.

### Reasoner reference

- [Graph reasoner](https://docs.relational.ai/) — node-concept and edge-concept graph construction, reachability, betweenness centrality, and path enumeration.

### More templates

- [Template gallery](https://docs.relational.ai/build/templates) — other runnable templates, including graph, rules, and prescriptive examples.

## Support

- Questions or issues: [support.relational.ai](https://support.relational.ai).
- File issues at the RelationalAI templates repository.
Loading
Loading