-
Notifications
You must be signed in to change notification settings - Fork 440
feat: Add supply-chain-risk-oracle kit. #277
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
KURO-1125
wants to merge
5
commits into
Lamatic:main
Choose a base branch
from
KURO-1125:feat/supply-chain-risk-oracle
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
6482090
feat: Add supply-chain-risk-oracle kit
KURO-1125 122879f
feat: add scripts for supply-chain-scan flow
KURO-1125 bbc649d
feat: add vercel demo URL
KURO-1125 69020f2
fix: address CodeRabbit review comments
KURO-1125 4e67dbb
fix: address remaining CodeRabbit review comments
KURO-1125 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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= |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| .lamatic/ | ||
| node_modules/ | ||
| .next/ | ||
| .env | ||
| .env.local |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| .next/ | ||
| node_modules/ | ||
| .env.* | ||
| !.env.example | ||
| .vercel | ||
128 changes: 128 additions & 0 deletions
128
kits/supply-chain-risk-oracle/apps/actions/orchestrate.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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"; | ||
|
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(), | ||
| }); | ||
|
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 ?? "", | ||
| }, | ||
| }; | ||
|
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) }; | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.