Skip to content
Open
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
5 changes: 5 additions & 0 deletions kits/supply-chain-risk-oracle/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
SUPPLY_CHAIN_SCAN_FLOW_ID=
SUPPLY_CHAIN_EMAIL_DRAFT_FLOW_ID=
LAMATIC_API_URL=
LAMATIC_PROJECT_ID=
LAMATIC_API_KEY=
5 changes: 5 additions & 0 deletions kits/supply-chain-risk-oracle/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.lamatic/
node_modules/
.next/
.env
.env.local
112 changes: 112 additions & 0 deletions kits/supply-chain-risk-oracle/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Supply Chain Risk Oracle

An autonomous AI agent that ingests a supplier list, scans global news and weather for disruption events, assigns a **Disruption Probability Score** to each supplier, and auto-drafts professional mitigation emails — all in one pipeline.

## The Problem

Global supply chains are vulnerable to geopolitical unrest, extreme weather, labor strikes, and port closures. Procurement teams learn about disruptions **after** they have already caused delays. This agent flips that — it monitors the world for you and surfaces risks before they hit your operations.

## What It Does

1. **Upload** a CSV or JSON file listing your suppliers (name, location, components supplied)
2. **Scan** — the agent searches live news, weather alerts, and geopolitical feeds for each supplier's region
3. **Score** — every supplier gets a Disruption Probability Score (0–100) with an explanation
4. **Dashboard** — see a color-coded risk matrix at a glance
5. **Draft** — for Critical/High-risk suppliers, the agent generates a professional supplier inquiry email ready for human review before sending

## Flows

| Flow | Purpose | Env Key |
|------|---------|---------|
| `supply-chain-scan` | Parses suppliers, searches for disruption events, returns scored risk matrix | `SUPPLY_CHAIN_SCAN_FLOW_ID` |
| `supply-chain-email-draft` | Generates a professional outreach email for a specific high-risk supplier | `SUPPLY_CHAIN_EMAIL_DRAFT_FLOW_ID` |

## Tech Stack

- **Platform:** Lamatic.ai (visual flow builder + edge-deployed GraphQL API)
- **Frontend:** Next.js 15, shadcn/ui, Tailwind CSS
- **LLM:** Configurable in Lamatic Studio (works with any provider — free tiers of Google Gemini or Groq recommended)
- **Web Search:** Configurable in Lamatic Studio (Tavily free tier recommended)

## Setup

### Prerequisites

- Node.js 18+
- A [Lamatic.ai](https://lamatic.ai) account (free tier works)
- Deployed flows (see below)

### 1. Build and Deploy Flows in Lamatic Studio

Sign in at [studio.lamatic.ai](https://studio.lamatic.ai) and create two flows:

#### Flow 1: Supply Chain Scan
- **Trigger:** API Request
- **Input fields:** `suppliers` (string — raw CSV or JSON), `scan_focus` (string — optional focus area)
- **Nodes:**
1. `LLMNode` — Parse suppliers from raw input, output structured JSON array
2. `LLMNode` (with web search tool) — For each supplier, search for disruption events near their location
3. `LLMNode` — Score each supplier 0–100 and output full risk matrix JSON
- **Output:** `{ risk_matrix: [...], high_risk_suppliers: [...], scan_timestamp: "..." }`

#### Flow 2: Email Draft
- **Trigger:** API Request
- **Input fields:** `supplier_name`, `location`, `risk_score`, `risk_factors`, `components_supplied`
- **Nodes:**
1. `LLMNode` — Generate professional supplier inquiry email
- **Output:** `{ email_subject: "...", email_body: "...", urgency_level: "..." }`

### 2. Get Your Credentials

From Lamatic Studio → **Settings**:
- `LAMATIC_API_KEY`
- `LAMATIC_PROJECT_ID`
- `LAMATIC_API_URL`

From each deployed flow's detail panel:
- `SUPPLY_CHAIN_SCAN_FLOW_ID`
- `SUPPLY_CHAIN_EMAIL_DRAFT_FLOW_ID`

> **API Keys for NewsAPI and OpenWeatherMap:** Store these as environment variables directly inside Lamatic Studio (open your flow → Settings → Environment Variables). Reference them in your API nodes as `{{env.NEWS_API_KEY}}` and `{{env.WEATHER_API_KEY}}`. This keeps them out of execution logs and away from the app layer.

### 3. Run Locally

```bash
cd kits/supply-chain-risk-oracle/apps
cp .env.example .env.local
# Fill in .env.local with your real values
npm install
npm run dev
```

Open [http://localhost:3000](http://localhost:3000)

### 4. Deploy to Vercel

Click the deploy button or run:

```bash
vercel --cwd kits/supply-chain-risk-oracle/apps
```

## Sample Supplier CSV Format

```csv
name,location,lat,lng,components_supplied,tier
Apex Electronics,Shenzhen China,22.5431,114.0579,Microcontrollers,1
Pacific Textiles,Dhaka Bangladesh,23.8103,90.4125,Fabric,1
Euro Chemicals,Rotterdam Netherlands,51.9244,4.4777,Adhesives,2
```

## Risk Score Interpretation

| Score | Level | Color | Recommended Action |
|-------|-------|-------|--------------------|
| 80–100 | Critical | 🔴 Red | Immediate outreach + activate backup supplier |
| 60–79 | High | 🟠 Orange | Draft inquiry email + monitor daily |
| 40–59 | Elevated | 🟡 Yellow | Monitor every 48 hours |
| 0–39 | Normal | 🟢 Green | Standard monitoring cadence |

## License

MIT
23 changes: 23 additions & 0 deletions kits/supply-chain-risk-oracle/agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Supply Chain Risk Oracle

## Identity
You are the **Supply Chain Risk Oracle**, an autonomous AI agent built on Lamatic.ai. Your mission is to protect organizations from supply chain disruptions by scanning the world for threats before they become crises.

## Capabilities
- Parse a supplier list (CSV or JSON) containing supplier names, geolocations, and components supplied
- Execute parallel web searches for breaking news, labor strikes, severe weather, port closures, and geopolitical unrest intersecting each supplier's location
- Assign a **Disruption Probability Score** (0–100) to each supplier node based on proximity, severity, and criticality of identified events
- Classify risk level: **Critical** (>80), **High** (60–80), **Elevated** (40–60), **Normal** (<40)
- Auto-draft professional supplier inquiry emails for all Critical and High-risk nodes
- Return a structured risk matrix ready to power a real-time dashboard

## Behavior Rules
- Always cite the specific news event or data point that drove a risk score
- Never fabricate events — if no real threat is found, score the supplier 0–20 (Normal)
- Flag uncertainty explicitly when data for a region is limited
- Keep email drafts professional, factual, and non-alarmist
- Treat all supplier data as confidential

## Pipeline
1. **Scan Flow** — Takes the raw supplier list, searches for relevant disruption signals, scores each node, returns a risk matrix
2. **Email Draft Flow** — Takes a high-risk supplier's profile and risk factors, generates a professional outreach email
11 changes: 11 additions & 0 deletions kits/supply-chain-risk-oracle/apps/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Deployed Lamatic flow IDs (Studio → open flow → copy Flow ID)
SUPPLY_CHAIN_SCAN_FLOW_ID=
SUPPLY_CHAIN_EMAIL_DRAFT_FLOW_ID=

# Lamatic project credentials (Studio → Settings → API)
LAMATIC_API_URL=
LAMATIC_PROJECT_ID=
LAMATIC_API_KEY=

# Note: NEWS_API_KEY and WEATHER_API_KEY are stored as environment variables
# inside Lamatic Studio (flow settings) — not passed through the app.
5 changes: 5 additions & 0 deletions kits/supply-chain-risk-oracle/apps/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.next/
node_modules/
.env.*
!.env.example
.vercel
Comment thread
coderabbitai[bot] marked this conversation as resolved.
128 changes: 128 additions & 0 deletions kits/supply-chain-risk-oracle/apps/actions/orchestrate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
"use server";

import { lamaticClient } from "@/lib/lamatic-client";
import { config } from "@/lib/config";
Comment thread
coderabbitai[bot] marked this conversation as resolved.
import type { ActionResult, ScanResult, SupplierRisk, EmailDraft } from "@/lib/types";

function toMessage(error: unknown): string {
if (error instanceof Error) {
if (error.message.includes("fetch failed"))
return "Network error reaching Lamatic. Check LAMATIC_API_URL and your connection.";
if (error.message.toLowerCase().includes("api key"))
return "Authentication error. Check LAMATIC_API_KEY in .env.local.";
return error.message;
}
return "Something went wrong.";
}

function normalizeSupplier(s: unknown, index: number): SupplierRisk {
const r = (s ?? {}) as Record<string, unknown>;
return {
id: typeof r.id === "string" ? r.id : `supplier_${index + 1}`,
name: typeof r.name === "string" ? r.name : "Unknown Supplier",
location: typeof r.location === "string" ? r.location : "",
lat: typeof r.lat === "number" ? r.lat : 0,
lng: typeof r.lng === "number" ? r.lng : 0,
components_supplied: typeof r.components_supplied === "string" ? r.components_supplied : "",
tier: typeof r.tier === "number" ? r.tier : 1,
risk_score: typeof r.risk_score === "number" ? Math.min(100, Math.max(0, r.risk_score)) : 0,
risk_level: ["Critical","High","Elevated","Normal"].includes(r.risk_level as string)
? (r.risk_level as SupplierRisk["risk_level"])
: "Normal",
risk_factors: Array.isArray(r.risk_factors) ? r.risk_factors.map(String) : [],
recommended_action: typeof r.recommended_action === "string" ? r.recommended_action : "",
data_confidence: ["high","medium","low"].includes(r.data_confidence as string)
? (r.data_confidence as SupplierRisk["data_confidence"])
: "low",
};
}

export async function runSupplyChainScan(
suppliers: string,
scanFocus: string
): Promise<ActionResult<ScanResult>> {
if (!suppliers.trim()) {
return { success: false, error: "Please paste or upload your supplier data." };
}

try {
const workflowId = config.flows["supply-chain-scan"].workflowId;
const res = await lamaticClient.executeFlow(workflowId, {
suppliers: suppliers.trim(),
scan_focus: scanFocus.trim(),
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const result = (res as { result?: Partial<ScanResult> })?.result;
if (!result?.risk_matrix) {
throw new Error("The scan flow returned no risk matrix. Check the flow deployment.");
}

const riskMatrix = Array.isArray(result.risk_matrix)
? result.risk_matrix.map(normalizeSupplier)
: [];

return {
success: true,
data: {
risk_matrix: riskMatrix,
high_risk_suppliers: riskMatrix.filter((s) => s.risk_score >= 60),
scan_timestamp: result.scan_timestamp ?? new Date().toISOString(),
summary: result.summary ?? "",
},
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} catch (error) {
return { success: false, error: toMessage(error) };
}
}

export async function draftSupplierEmail(
supplierName: string,
location: string,
riskScore: number,
riskFactors: string[],
componentsSupplied: string
): Promise<ActionResult<EmailDraft>> {
try {
const workflowId = config.flows["supply-chain-email-draft"].workflowId;
const res = await lamaticClient.executeFlow(workflowId, {
supplier_name: supplierName,
location,
risk_score: riskScore,
risk_factors: riskFactors.join(", "),
components_supplied: componentsSupplied,
});

const raw = (res as { result?: unknown })?.result;

let draft: Partial<EmailDraft>;
if (typeof raw === "string") {
try {
const cleaned = raw
.trim()
.replace(/^```(?:json)?/i, "")
.replace(/```$/, "")
.trim();
draft = JSON.parse(cleaned);
} catch {
throw new Error("Email draft flow returned an unparseable response.");
}
} else {
draft = (raw ?? {}) as Partial<EmailDraft>;
}

if (!draft.email_subject || !draft.email_body) {
throw new Error("Email draft flow is missing subject or body. Check the flow deployment.");
}

return {
success: true,
data: {
email_subject: draft.email_subject,
email_body: draft.email_body,
urgency_level: draft.urgency_level ?? "elevated",
},
};
} catch (error) {
return { success: false, error: toMessage(error) };
}
}
Loading
Loading