node-red-contrib-vectorprime 0.1.42 → 0.1.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -42
- package/package.json +1 -1
- package/vectorprime.html +58 -28
- package/vectorprime.js +35 -56
package/README.md
CHANGED
|
@@ -1,57 +1,29 @@
|
|
|
1
1
|
# node-red-contrib-vectorprime
|
|
2
|
-
### VectorPrime — Decision Ranking for Node-RED
|
|
3
2
|
|
|
4
|
-
**node
|
|
3
|
+
✅ **1-minute setup:** install the node → drag **Rank Decision (VectorPrime)** into a flow → open **VectorPrime Config** → get your API key → paste → Deploy.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
👉 **Get your API key:** https://vectorprime.tech/?utm_source=node-red&utm_medium=readme&utm_campaign=node_red_contrib&utm_content=top_cta
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
## 🧠 What this node does (plain English)
|
|
11
|
-
|
|
12
|
-
You send VectorPrime a list of options (tasks, actions, alerts). Each option can include simple metadata like urgency, impact, deadline, or effort.
|
|
13
|
-
|
|
14
|
-
VectorPrime returns:
|
|
15
|
-
- A ranked list (best → worst)
|
|
16
|
-
- Probabilities / confidence signals (when provided)
|
|
17
|
-
- Metadata about the ranking
|
|
18
|
-
|
|
19
|
-
This turns Node-RED into a **decision-aware automation engine**, not just `if/else`.
|
|
7
|
+
> Pricing note: This node is free to use. VectorPrime API usage may require a paid plan after free-tier limits.
|
|
20
8
|
|
|
21
9
|
---
|
|
22
10
|
|
|
23
|
-
##
|
|
24
|
-
|
|
25
|
-
- **VectorPrime Config**
|
|
26
|
-
Stores Base URL + API key securely using Node-RED credentials (not exported with flows).
|
|
11
|
+
## What this node does
|
|
27
12
|
|
|
28
|
-
- **
|
|
29
|
-
Sends options to the VectorPrime Rank API and returns ranked results.
|
|
30
|
-
|
|
31
|
-
---
|
|
13
|
+
VectorPrime helps Node-RED flows choose the **best next action** when multiple options compete — without hard-coded rules.
|
|
32
14
|
|
|
33
|
-
|
|
15
|
+
Use cases:
|
|
34
16
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
5. Drag **Rank Decision (VectorPrime)** into a flow
|
|
40
|
-
6. Create/select a **VectorPrime Config** and click **Get Free Key**
|
|
41
|
-
7. Inject one of the examples below into `msg.payload`
|
|
17
|
+
- Prioritize tasks
|
|
18
|
+
- Rank alerts by urgency/impact
|
|
19
|
+
- Choose the safest next step in automation flows
|
|
20
|
+
- Route tickets/jobs based on structured decision input
|
|
42
21
|
|
|
43
22
|
---
|
|
44
23
|
|
|
45
|
-
##
|
|
24
|
+
## Install
|
|
46
25
|
|
|
47
|
-
|
|
26
|
+
In your Node-RED user directory (usually `C:\Users\<you>\.node-red`):
|
|
48
27
|
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
"items": [
|
|
52
|
-
{ "id": "payroll", "label": "Fix payroll outage", "urgency": 10, "impact": 10, "effort": 3 },
|
|
53
|
-
{ "id": "email", "label": "Reply to customer email", "urgency": 4, "impact": 5, "effort": 2 },
|
|
54
|
-
{ "id": "deploy", "label": "Ship small hotfix", "urgency": 7, "impact": 7, "effort": 4 }
|
|
55
|
-
],
|
|
56
|
-
"prompt": "Rank the best next action."
|
|
57
|
-
}
|
|
28
|
+
```bash
|
|
29
|
+
npm install node-red-contrib-vectorprime
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-vectorprime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.43",
|
|
4
4
|
"description": "Rank tasks, alerts, or actions inside Node-RED when multiple options compete. Choose the safest next step automatically instead of hard-coded rules.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Faisal Khan",
|
package/vectorprime.html
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
|
|
3
|
+
# 2) REPLACE: `vectorprime.html` (FULL FILE)
|
|
4
|
+
|
|
5
|
+
```html
|
|
1
6
|
<!-- =========================
|
|
2
|
-
VectorPrime
|
|
7
|
+
VectorPrime Nodes (Node-RED)
|
|
3
8
|
WEBSITE-ONLY KEYS (no minting inside Node-RED)
|
|
4
9
|
========================= -->
|
|
10
|
+
|
|
11
|
+
<!-- =========================
|
|
12
|
+
Config Node UI
|
|
13
|
+
========================= -->
|
|
5
14
|
<script type="text/x-red" data-template-name="vectorprime-config">
|
|
6
15
|
<div class="form-row">
|
|
7
16
|
<label for="node-config-input-baseUrl">
|
|
@@ -17,11 +26,20 @@
|
|
|
17
26
|
<input type="password" id="node-config-input-apiKey" placeholder="vp_live_...">
|
|
18
27
|
</div>
|
|
19
28
|
|
|
29
|
+
<div class="form-row">
|
|
30
|
+
<a
|
|
31
|
+
class="vp-cta-btn"
|
|
32
|
+
href="https://vectorprime.tech/?utm_source=node-red&utm_medium=config&utm_campaign=node_red_contrib&utm_content=get_key_button"
|
|
33
|
+
target="_blank"
|
|
34
|
+
rel="noopener noreferrer"
|
|
35
|
+
>
|
|
36
|
+
Get your API key on vectorprime.tech
|
|
37
|
+
</a>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
20
40
|
<div class="form-row">
|
|
21
41
|
<div style="font-size:12px; color:#666; line-height:1.4;">
|
|
22
|
-
|
|
23
|
-
<a href="https://vectorprime.tech" target="_blank" rel="noopener noreferrer">vectorprime.tech</a>,
|
|
24
|
-
then paste it above.
|
|
42
|
+
Then paste it into <b>API Key</b> above.
|
|
25
43
|
</div>
|
|
26
44
|
</div>
|
|
27
45
|
|
|
@@ -30,6 +48,21 @@
|
|
|
30
48
|
✅ Key is stored securely in Node-RED credentials (not exported with flows).
|
|
31
49
|
</div>
|
|
32
50
|
</div>
|
|
51
|
+
|
|
52
|
+
<style>
|
|
53
|
+
.vp-cta-btn{
|
|
54
|
+
display:block;
|
|
55
|
+
width:100%;
|
|
56
|
+
padding:10px 12px;
|
|
57
|
+
text-align:center;
|
|
58
|
+
background:#2563EB;
|
|
59
|
+
color:#fff !important;
|
|
60
|
+
border-radius:8px;
|
|
61
|
+
text-decoration:none;
|
|
62
|
+
font-weight:600;
|
|
63
|
+
}
|
|
64
|
+
.vp-cta-btn:hover{ filter:brightness(0.92); }
|
|
65
|
+
</style>
|
|
33
66
|
</script>
|
|
34
67
|
|
|
35
68
|
<script type="text/javascript">
|
|
@@ -74,8 +107,8 @@
|
|
|
74
107
|
|
|
75
108
|
<h3>Quick Setup</h3>
|
|
76
109
|
<ol>
|
|
77
|
-
<li>
|
|
78
|
-
<li>Get your API key
|
|
110
|
+
<li>Confirm <b>Base URL</b> (default is correct)</li>
|
|
111
|
+
<li>Click <b>Get your API key</b> (opens vectorprime.tech)</li>
|
|
79
112
|
<li>Paste the key into <b>API Key</b></li>
|
|
80
113
|
<li>Click <b>Done</b></li>
|
|
81
114
|
</ol>
|
|
@@ -86,12 +119,14 @@
|
|
|
86
119
|
|
|
87
120
|
<h3>Recommended Base URL</h3>
|
|
88
121
|
<pre>https://vectorprime-kernel-backend.onrender.com</pre>
|
|
122
|
+
|
|
123
|
+
<p><b>Website-only keys:</b> this node does not mint keys inside Node-RED.</p>
|
|
89
124
|
</script>
|
|
90
125
|
|
|
91
126
|
<hr />
|
|
92
127
|
|
|
93
128
|
<!-- =========================
|
|
94
|
-
|
|
129
|
+
Rank Node UI
|
|
95
130
|
========================= -->
|
|
96
131
|
<script type="text/x-red" data-template-name="vectorprime-rank">
|
|
97
132
|
<div class="form-row">
|
|
@@ -128,10 +163,7 @@
|
|
|
128
163
|
inputs: 1,
|
|
129
164
|
outputs: 1,
|
|
130
165
|
icon: "font-awesome/fa-bolt",
|
|
131
|
-
|
|
132
|
-
// ✅ This is the name users see in the palette
|
|
133
166
|
paletteLabel: "Rank Decision (VectorPrime)",
|
|
134
|
-
|
|
135
167
|
label: function () {
|
|
136
168
|
return this.name || "Rank Decision (VectorPrime)";
|
|
137
169
|
}
|
|
@@ -140,39 +172,37 @@
|
|
|
140
172
|
|
|
141
173
|
<!-- ✅ HELP TAB: vectorprime-rank -->
|
|
142
174
|
<script type="text/x-red" data-help-name="vectorprime-rank">
|
|
143
|
-
<p>
|
|
144
|
-
<b>Rank Decision (VectorPrime)</b> picks the best next action automatically.
|
|
145
|
-
Use it anywhere your flow needs <b>priority scoring</b> or <b>decision ranking</b>.
|
|
146
|
-
</p>
|
|
175
|
+
<p><b>Rank Decision (VectorPrime)</b> ranks options (tasks/actions/alerts) using the VectorPrime kernel.</p>
|
|
147
176
|
|
|
148
177
|
<h3>What it does</h3>
|
|
149
178
|
<ul>
|
|
150
179
|
<li>Reads options from <code>msg.payload</code></li>
|
|
151
|
-
<li>Calls the VectorPrime
|
|
180
|
+
<li>Calls the VectorPrime kernel rank API</li>
|
|
152
181
|
<li>Returns the ranked decision inside <code>msg.payload</code></li>
|
|
153
182
|
</ul>
|
|
154
183
|
|
|
155
|
-
<h3>Where developers use this most</h3>
|
|
156
|
-
<ul>
|
|
157
|
-
<li><b>Incident response:</b> rank alerts → fix the most critical first</li>
|
|
158
|
-
<li><b>Automation flows:</b> choose the best next action from multiple tasks</li>
|
|
159
|
-
<li><b>Ops / IoT:</b> prioritize device events + maintenance decisions</li>
|
|
160
|
-
<li><b>Ticketing & work queues:</b> rank jobs, leads, or support tickets</li>
|
|
161
|
-
</ul>
|
|
162
|
-
|
|
163
184
|
<h3>Input format (simple)</h3>
|
|
164
185
|
<p>Send a list to rank inside <code>msg.payload.items</code>:</p>
|
|
165
186
|
|
|
166
187
|
<pre><code>{
|
|
167
188
|
"items": [
|
|
168
|
-
{ "id": "
|
|
169
|
-
{ "id": "
|
|
170
|
-
{ "id": "
|
|
189
|
+
{ "id": "a", "label": "Fix production bug", "urgency": 9, "impact": 10, "effort": 3 },
|
|
190
|
+
{ "id": "b", "label": "Ship new feature", "urgency": 6, "impact": 7, "effort": 6 },
|
|
191
|
+
{ "id": "c", "label": "Refactor module", "urgency": 3, "impact": 4, "effort": 8 }
|
|
171
192
|
]
|
|
172
193
|
}</code></pre>
|
|
173
194
|
|
|
174
195
|
<p>
|
|
175
|
-
✅ The node automatically converts <code>items</code> into
|
|
176
|
-
|
|
196
|
+
✅ The node automatically converts <code>items</code> into:
|
|
197
|
+
<code>decision_id</code>, <code>prompt</code>, <code>options</code>.
|
|
177
198
|
</p>
|
|
199
|
+
|
|
200
|
+
<h3>Output</h3>
|
|
201
|
+
<p>On success, the node sets <code>msg.payload</code> to the kernel response, typically including:</p>
|
|
202
|
+
<ul>
|
|
203
|
+
<li><code>ranking</code> (best → worst)</li>
|
|
204
|
+
<li><code>probabilities</code> (per option id)</li>
|
|
205
|
+
<li><code>meta</code> (engine info / tie-break flags)</li>
|
|
206
|
+
<li><code>vp_input</code> (what was sent)</li>
|
|
207
|
+
</ul>
|
|
178
208
|
</script>
|
package/vectorprime.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module.exports = function (RED) {
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
|
-
//
|
|
4
|
+
// Node 18+ has global fetch. Keep fallback for older Node.
|
|
5
5
|
const fetchFn =
|
|
6
6
|
typeof fetch === "function"
|
|
7
7
|
? fetch
|
|
@@ -58,10 +58,9 @@ module.exports = function (RED) {
|
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
// Convert to pseudo-probabilities (normalized non-negative)
|
|
61
|
-
// Make everything >= 0 by shifting if needed
|
|
62
61
|
const scores = scored.map(s => s.score);
|
|
63
62
|
const minScore = Math.min(...scores);
|
|
64
|
-
const shifted = scores.map(s => s - minScore); //
|
|
63
|
+
const shifted = scores.map(s => s - minScore); // min is 0
|
|
65
64
|
const sum = shifted.reduce((acc, v) => acc + v, 0);
|
|
66
65
|
|
|
67
66
|
const probabilities = {};
|
|
@@ -70,7 +69,6 @@ module.exports = function (RED) {
|
|
|
70
69
|
probabilities[s.id] = shifted[j] / sum;
|
|
71
70
|
});
|
|
72
71
|
} else {
|
|
73
|
-
// all zero (fully tied) -> uniform
|
|
74
72
|
const uniform = 1 / scored.length;
|
|
75
73
|
scored.forEach(s => { probabilities[s.id] = uniform; });
|
|
76
74
|
}
|
|
@@ -83,7 +81,7 @@ module.exports = function (RED) {
|
|
|
83
81
|
|
|
84
82
|
// -----------------------------
|
|
85
83
|
// Config Node (stores Base URL + API Key)
|
|
86
|
-
// WEBSITE-ONLY KEYS: no
|
|
84
|
+
// WEBSITE-ONLY KEYS: no minting inside Node-RED
|
|
87
85
|
// -----------------------------
|
|
88
86
|
function VectorPrimeConfigNode(n) {
|
|
89
87
|
RED.nodes.createNode(this, n);
|
|
@@ -91,9 +89,7 @@ module.exports = function (RED) {
|
|
|
91
89
|
this.baseUrl =
|
|
92
90
|
(n.baseUrl && String(n.baseUrl).trim()) ||
|
|
93
91
|
"https://vectorprime-kernel-backend.onrender.com";
|
|
94
|
-
|
|
95
|
-
// NOTE: apiKey is stored in credentials only.
|
|
96
|
-
// No email stored here anymore (website-only keys).
|
|
92
|
+
// apiKey is stored in credentials only.
|
|
97
93
|
}
|
|
98
94
|
|
|
99
95
|
RED.nodes.registerType("vectorprime-config", VectorPrimeConfigNode, {
|
|
@@ -120,9 +116,7 @@ module.exports = function (RED) {
|
|
|
120
116
|
try {
|
|
121
117
|
if (!cfg) {
|
|
122
118
|
node.status({ fill: "red", shape: "ring", text: "missing config" });
|
|
123
|
-
throw new Error(
|
|
124
|
-
"VectorPrime config missing. Open node settings and select/create a Config."
|
|
125
|
-
);
|
|
119
|
+
throw new Error("VectorPrime config missing. Open node settings and select/create a Config.");
|
|
126
120
|
}
|
|
127
121
|
|
|
128
122
|
const baseUrl = normalizeBaseUrl(cfg.baseUrl);
|
|
@@ -141,15 +135,14 @@ module.exports = function (RED) {
|
|
|
141
135
|
if (!storedKey) {
|
|
142
136
|
node.status({ fill: "red", shape: "ring", text: "missing api key" });
|
|
143
137
|
throw new Error(
|
|
144
|
-
"No API key. Get
|
|
138
|
+
"No API key. Get one at https://vectorprime.tech/?utm_source=node-red&utm_medium=runtime_error&utm_campaign=node_red_contrib&utm_content=missing_key then paste it into the VectorPrime Config."
|
|
145
139
|
);
|
|
146
140
|
}
|
|
147
141
|
|
|
148
|
-
//
|
|
149
|
-
//
|
|
142
|
+
// ------------------------------------
|
|
143
|
+
// AUTO-CONVERT items -> options (friendly input)
|
|
150
144
|
// Backend expects: decision_id, prompt, options
|
|
151
|
-
//
|
|
152
|
-
// -----------------------------
|
|
145
|
+
// ------------------------------------
|
|
153
146
|
const originalPayload = msg.payload;
|
|
154
147
|
let payload = msg.payload || {};
|
|
155
148
|
let inputOptionsForFallback = null;
|
|
@@ -181,12 +174,14 @@ module.exports = function (RED) {
|
|
|
181
174
|
inputOptionsForFallback = payload.options;
|
|
182
175
|
}
|
|
183
176
|
|
|
184
|
-
|
|
185
|
-
if (!payload.decision_id || !payload.prompt || !payload.options) {
|
|
177
|
+
if (!payload || typeof payload !== "object") {
|
|
186
178
|
node.status({ fill: "red", shape: "ring", text: "bad payload" });
|
|
187
|
-
throw new Error(
|
|
188
|
-
|
|
189
|
-
|
|
179
|
+
throw new Error("Invalid payload. Provide { items:[...] } or full { decision_id, prompt, options }.");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!payload.decision_id || !payload.prompt || !payload.options || !Array.isArray(payload.options)) {
|
|
183
|
+
node.status({ fill: "red", shape: "ring", text: "bad payload" });
|
|
184
|
+
throw new Error("Invalid payload. Provide { items:[...] } or full { decision_id, prompt, options }.");
|
|
190
185
|
}
|
|
191
186
|
|
|
192
187
|
node.status({ fill: "blue", shape: "dot", text: "ranking..." });
|
|
@@ -209,54 +204,38 @@ module.exports = function (RED) {
|
|
|
209
204
|
throw new Error(`VectorPrime API ${resp.status}: ${JSON.stringify(data)}`);
|
|
210
205
|
}
|
|
211
206
|
|
|
212
|
-
//
|
|
213
|
-
// ✅ If backend returns a tie/flat result, do deterministic local ranking
|
|
214
|
-
// -----------------------------
|
|
207
|
+
// If backend returns a tie/flat result, do deterministic local ranking
|
|
215
208
|
const tieBreak =
|
|
216
209
|
data && data.meta && typeof data.meta === "object"
|
|
217
210
|
? data.meta.tie_break
|
|
218
211
|
: null;
|
|
219
212
|
|
|
220
|
-
const hasProb = data && data.probabilities;
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
(
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
// replace with deterministic result
|
|
233
|
-
data = {
|
|
234
|
-
...data,
|
|
235
|
-
ranking: fallback.ranking,
|
|
236
|
-
probabilities: fallback.probabilities,
|
|
237
|
-
meta: {
|
|
238
|
-
...(data.meta || {}),
|
|
239
|
-
notes: (data.meta && data.meta.notes ? String(data.meta.notes) + " | " : "") +
|
|
240
|
-
"Node-RED fallback ranking applied (urgency/impact/effort) because backend result was tied/flat.",
|
|
241
|
-
numeric_source: "node_red_fallback",
|
|
242
|
-
tie_break: "fallback_score_then_preserve_input_order",
|
|
243
|
-
},
|
|
244
|
-
};
|
|
213
|
+
const hasProb = data && data.probabilities && typeof data.probabilities === "object";
|
|
214
|
+
const isFlat = hasProb ? isFlatProbabilities(data.probabilities) : false;
|
|
215
|
+
|
|
216
|
+
if ((tieBreak || isFlat) && Array.isArray(inputOptionsForFallback) && inputOptionsForFallback.length >= 2) {
|
|
217
|
+
const local = buildFallbackRankingFromOptions(inputOptionsForFallback);
|
|
218
|
+
data = data && typeof data === "object" ? data : {};
|
|
219
|
+
data.ranking = local.ranking;
|
|
220
|
+
data.probabilities = local.probabilities;
|
|
221
|
+
data.meta = data.meta && typeof data.meta === "object" ? data.meta : {};
|
|
222
|
+
data.meta.local_fallback = true;
|
|
223
|
+
data.meta.local_fallback_reason = tieBreak ? "backend_tie_break" : "flat_probabilities";
|
|
245
224
|
}
|
|
246
225
|
|
|
247
|
-
|
|
226
|
+
// Attach what we sent (useful for debugging + audits)
|
|
227
|
+
if (data && typeof data === "object") {
|
|
228
|
+
data.vp_input = payload;
|
|
229
|
+
data.vp_original_payload = originalPayload;
|
|
230
|
+
}
|
|
248
231
|
|
|
249
|
-
// ✅ Replace payload with ranked result
|
|
250
232
|
msg.payload = data;
|
|
251
233
|
|
|
252
|
-
|
|
253
|
-
msg._vp_input = originalPayload;
|
|
254
|
-
|
|
234
|
+
node.status({ fill: "green", shape: "dot", text: "ok" });
|
|
255
235
|
send(msg);
|
|
256
236
|
done();
|
|
257
237
|
} catch (err) {
|
|
258
|
-
node.status({ fill: "red", shape: "ring", text: "
|
|
259
|
-
node.error(err, msg);
|
|
238
|
+
node.status({ fill: "red", shape: "ring", text: "error" });
|
|
260
239
|
done(err);
|
|
261
240
|
}
|
|
262
241
|
});
|