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 CHANGED
@@ -1,57 +1,29 @@
1
1
  # node-red-contrib-vectorprime
2
- ### VectorPrime — Decision Ranking for Node-RED
3
2
 
4
- **node-red-contrib-vectorprime** lets you rank tasks, alerts, or actions inside Node-RED when multiple options compete.
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
- Instead of hard-coding rules, VectorPrime helps flows choose the **safest, most urgent, or most rational next step** automatically.
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
- ## 📦 Nodes included
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
- - **Rank Decision (VectorPrime)**
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
- ## ⚡ Quick start (5 minutes)
15
+ Use cases:
34
16
 
35
- 1. Open Node-RED
36
- 2. Menu **Manage Palette** → **Install**
37
- 3. Search for: **node-red-contrib-vectorprime**
38
- 4. Install
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
- ## ✅ Input format (simple) — `items[]` (recommended)
24
+ ## Install
46
25
 
47
- The node will automatically convert `items[]` into the backend’s required `options[]` format.
26
+ In your Node-RED user directory (usually `C:\Users\<you>\.node-red`):
48
27
 
49
- ```json
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.42",
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 Config Template
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
- Get your API key from
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>Set <b>Base URL</b> (default is already correct)</li>
78
- <li>Get your API key from <b>vectorprime.tech</b></li>
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
- VectorPrime Rank Template
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 Decision Kernel Rank API</li>
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": "fix-prod-bug", "label": "Fix production bug", "urgency": 10, "impact": 10 },
169
- { "id": "ship-feature", "label": "Ship new feature", "urgency": 7, "impact": 8 },
170
- { "id": "refactor", "label": "Refactor module", "urgency": 3, "impact": 5 }
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 the backend format
176
- (<code>decision_id</code>, <code>prompt</code>, <code>options</code>) so flows stay easy.
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
- // Node 22 has global fetch, but keep fallback for safety
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); // now min is 0
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 email, no "Get Free Key" minting
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 your key from vectorprime.tech and paste it into the VectorPrime Config."
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
- // AUTO-CONVERT ITEMS OPTIONS
142
+ // ------------------------------------
143
+ // AUTO-CONVERT items -> options (friendly input)
150
144
  // Backend expects: decision_id, prompt, options
151
- // Users send: {items:[...]}
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
- // Validate final expected shape
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
- "Invalid payload. Provide { items:[...] } or full { decision_id, prompt, options }."
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
- const looksTied =
223
- (tieBreak === "preserve_input_order" && isFlatProbabilities(hasProb)) ||
224
- (isFlatProbabilities(hasProb));
225
-
226
- if (looksTied && Array.isArray(inputOptionsForFallback) && inputOptionsForFallback.length > 0) {
227
- const fallback = buildFallbackRankingFromOptions(inputOptionsForFallback);
228
-
229
- // keep raw backend response for debugging
230
- msg.vp_raw = data;
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
- node.status({ fill: "green", shape: "dot", text: "ok" });
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
- // preserve original input for debugging (no breaking changes)
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: "failed" });
259
- node.error(err, msg);
238
+ node.status({ fill: "red", shape: "ring", text: "error" });
260
239
  done(err);
261
240
  }
262
241
  });