diff --git a/pipeline.js b/pipeline.js
index d913152..b2ca7ab 100644
--- a/pipeline.js
+++ b/pipeline.js
@@ -9,7 +9,21 @@ const fs = require('fs');
const path = require('path');
const db = require('./db');
-let DATA_DIR = null, accounts = null, coach = null;
+let DATA_DIR = null, accounts = null, coach = null, chain = null;
+// linked positions (Qualified Start) belong to the member: their buyers count toward the account, the same way
+// My line chips and the badges count them (Jim's card showed 1 buyer while his badge said Circuit, 2026-09-15)
+const posCache = new Map();
+async function positionBuyers(email) {
+ let ids = []; try { ids = (await accounts.positions(email)).map(p => p.memberId).filter(Boolean); } catch (e) {}
+ let n = 0;
+ for (const id of ids) {
+ const c = posCache.get(id); let v = c && Date.now() - c.t < 60000 ? c.v : null;
+ if (v == null) { try { v = (await chain.member(id)).buyerCount || 0; } catch (e) { v = 0; } posCache.set(id, { t: Date.now(), v }); }
+ n += v;
+ }
+ return { count: n, positions: ids.length };
+}
+function rungForTotal(rung, total) { if (rung < 3) return rung; return total === 0 ? 3 : total === 1 ? 4 : total < 5 ? 5 : 6; }
const DAY = 86400000;
// columns, in order. Prospects live in the first and last; directs land by coaching rung.
@@ -46,7 +60,7 @@ const D = {
};
const impl = () => db.enabled() ? D : J;
-function init(opts) { DATA_DIR = opts.dataDir; accounts = opts.accounts; coach = opts.coach; }
+function init(opts) { DATA_DIR = opts.dataDir; accounts = opts.accounts; coach = opts.coach; chain = opts.chain; }
// who may see the live board: mode on = everyone; preview = the admin account only
function visible(mode, email, adminEmail) {
@@ -79,13 +93,16 @@ async function board(email) {
for (const m of (levels[0] ? levels[0].members : [])) {
let c = null; try { c = await coach.describe(m, now); } catch (e) {}
const n = notes[m.email] || {};
- const rung = c ? c.rung : 0;
+ const pb = c && c.rung >= 3 ? await positionBuyers(m.email) : { count: 0, positions: 0 };
+ const buyersMain = c ? c.buyerCount || 0 : 0, buyersAll = buyersMain + pb.count;
+ const rung = c ? rungForTotal(c.rung, buyersAll) : 0;
+ const RR = coach.RUNGS[rung];
const stage = PARKED.has(n.tag) && rung < 3 ? 'later' : RUNG_STAGE[rung] || 'joined';
cards.push({ key: m.email, kind: 'member', email: m.email, name: m.username ? '@' + m.username : (m.memberId ? 'member #' + m.memberId : 'member'),
- memberId: m.memberId || 0, stage, rung, rungLabel: c ? c.label : 'Joined', since: m.created || now,
- lastSeen: m.lastSeen || 0, quietDays: c ? c.quietDays : 0, stalled: !!(c && c.stalled),
- buyers: c ? c.buyerCount || 0 : 0, bought: !!(c && c.counted),
- next: c ? c.next : 'Link a wallet', say: c ? c.say.replace('{{name}}', m.username || 'there') : '',
+ memberId: m.memberId || 0, stage, rung, rungLabel: RR.label, since: m.created || now,
+ lastSeen: m.lastSeen || 0, quietDays: c ? c.quietDays : 0, stalled: !!(c && c.stalled && rung < 6),
+ buyers: buyersAll, buyersMain, buyersPositions: pb.count, positions: pb.positions, bought: !!(c && c.counted),
+ next: RR.next, say: RR.say.replace('{{name}}', m.username || 'there'),
note: n.note || '', followUp: n.followUp || null, tag: n.tag || '' });
}
const due = cards.filter(c => c.followUp && c.followUp <= eod.getTime()).sort((a, b) => a.followUp - b.followUp);
diff --git a/public/assets/my.js b/public/assets/my.js
index fc3c084..98f088e 100644
--- a/public/assets/my.js
+++ b/public/assets/my.js
@@ -1383,7 +1383,7 @@
function fillPipeCard(c) {
$('pipeCardName').textContent = c.name;
const stage = (PIPE.stages.find(s => s.key === c.stage) || {}).label || '';
- $('pipeCardMeta').textContent = stage + (c.kind === 'member' ? ' · joined ' + new Date(c.since).toLocaleDateString() + (c.lastSeen ? ' · last seen ' + pipeAgo(c.lastSeen) : ' · never signed in') + (c.bought ? ' · your qualifying buyer' : '') : ' · prospect' + (c.contact ? ' · ' + c.contact : ''));
+ $('pipeCardMeta').textContent = stage + (c.kind === 'member' ? ' · joined ' + new Date(c.since).toLocaleDateString() + (c.lastSeen ? ' · last seen ' + pipeAgo(c.lastSeen) : ' · never signed in') + (c.bought ? ' · your qualifying buyer' : '') + (c.buyersPositions ? ' · ' + c.buyers + ' buyers: ' + c.buyersMain + ' on the main wallet, ' + c.buyersPositions + ' on linked positions' : '') : ' · prospect' + (c.contact ? ' · ' + c.contact : ''));
$('pipeCardNext').innerHTML = 'Next for them: ' + esc(c.next) + (c.stalled ? ' quiet ' + c.quietDays + ' days' : '');
const say = (c.say || '').replace('{{link}}', pipeInvite());
$('pipeSayBlock').hidden = !say; $('pipeSay').textContent = say;
diff --git a/public/my.html b/public/my.html
index 855c705..6fd67e6 100644
--- a/public/my.html
+++ b/public/my.html
@@ -1021,7 +1021,7 @@
-
+