← back to AbramsEgo
feat(revenue): dated income ledger + POST /api/revenue/record (auth) + per-window P&L + snapshot race fix (coalesce in-flight build). Recorded income now flips engine live and moves self-funding % in real time (task 03)
df0f579b9f5169721e0d9df089239ae1232f6203 · 2026-07-01 10:50:30 -0700 · Steve (AbramsEgo)
Files touched
D build-queue/.loop.lock/pidA build-queue/STOPM server.js
Diff
commit df0f579b9f5169721e0d9df089239ae1232f6203
Author: Steve (AbramsEgo) <steve@designerwallcoverings.com>
Date: Wed Jul 1 10:50:30 2026 -0700
feat(revenue): dated income ledger + POST /api/revenue/record (auth) + per-window P&L + snapshot race fix (coalesce in-flight build). Recorded income now flips engine live and moves self-funding % in real time (task 03)
---
build-queue/.loop.lock/pid | 1 -
build-queue/STOP | 0
server.js | 115 ++++++++++++++++++++++++++++++++-------------
3 files changed, 83 insertions(+), 33 deletions(-)
diff --git a/build-queue/.loop.lock/pid b/build-queue/.loop.lock/pid
deleted file mode 100644
index 8f1a099..0000000
--- a/build-queue/.loop.lock/pid
+++ /dev/null
@@ -1 +0,0 @@
-32374
diff --git a/build-queue/STOP b/build-queue/STOP
new file mode 100644
index 0000000..e69de29
diff --git a/server.js b/server.js
index e161591..4b831db 100644
--- a/server.js
+++ b/server.js
@@ -30,6 +30,7 @@ const HOME = os.homedir();
const DATA_DIR = path.join(__dirname, 'data');
const SNAPSHOT = path.join(DATA_DIR, 'data.json');
const REVENUE_FILE = path.join(DATA_DIR, 'revenue.json');
+const REVENUE_LEDGER = path.join(DATA_DIR, 'revenue-ledger.jsonl');
const VERSION = require('./package.json').version;
const START_TS = Date.now();
@@ -294,17 +295,45 @@ function defaultRevenue() {
],
};
}
+// dated income rows: {ts, engine, amount, source} appended by POST /api/revenue/record.
+// Summed per-engine (all-time) and per-window so the P&L reflects REAL income by
+// today/week/month rather than a flat total.
+function readRevenueLedger() {
+ const now = Date.now();
+ const wins = { today: 24 * 3.6e6, week: 7 * 24 * 3.6e6, month: 30 * 24 * 3.6e6 };
+ const windowSums = { today: 0, week: 0, month: 0 };
+ const byEngine = {};
+ try {
+ const lines = fs.readFileSync(REVENUE_LEDGER, 'utf8').split('\n');
+ for (const ln of lines) {
+ if (!ln.trim()) continue;
+ let j; try { j = JSON.parse(ln); } catch (e) { continue; }
+ const t = Date.parse(j.ts); const a = Number(j.amount) || 0;
+ if (isNaN(t)) continue;
+ byEngine[j.engine] = (byEngine[j.engine] || 0) + a;
+ for (const [k, ms] of Object.entries(wins)) if (t >= now - ms) windowSums[k] += a;
+ }
+ } catch (e) {}
+ return { windowSums, byEngine };
+}
function collectRevenue() {
- try { return JSON.parse(fs.readFileSync(REVENUE_FILE, 'utf8')); }
- catch (e) { const d = defaultRevenue(); try { fs.writeFileSync(REVENUE_FILE, JSON.stringify(d, null, 2)); } catch (e2) {} return d; }
+ let base;
+ try { base = JSON.parse(fs.readFileSync(REVENUE_FILE, 'utf8')); }
+ catch (e) { base = defaultRevenue(); try { fs.writeFileSync(REVENUE_FILE, JSON.stringify(base, null, 2)); } catch (e2) {} }
+ const led = readRevenueLedger();
+ // fold real income into each engine; an engine with income is de-facto "live"
+ base.engines = (base.engines || []).map((e) => ({ ...e, amount: round(led.byEngine[e.key] || 0, 2), status: (led.byEngine[e.key] || 0) > 0 ? 'live' : e.status }));
+ base.windowSums = led.windowSums;
+ base.total = round(Object.values(led.byEngine).reduce((s, v) => s + v, 0), 2);
+ return base;
}
function buildPnL(cost, revenue) {
const out = {};
for (const win of ['today', 'week', 'month']) {
const c = (cost[win] && cost[win].total) || 0;
- // revenue is a running total; for windows we currently only have live totals → attribute all to "month", 0 else until engines log dated income.
- const rev = win === 'month' ? (revenue.engines || []).reduce((s, e) => s + (e.amount || 0), 0) : (revenue.engines || []).reduce((s, e) => s + (e.amount || 0), 0);
+ // real dated income per window from the revenue ledger (0 until engines earn).
+ const rev = round((revenue.windowSums && revenue.windowSums[win]) || 0, 4);
const net = round(rev - c, 4);
out[win] = { cost: round(c, 4), revenue: round(rev, 4), net, selfFundingPct: c > 0 ? round((rev / c) * 100, 1) : null };
}
@@ -325,37 +354,43 @@ function buildPnL(cost, revenue) {
// ---------------------------------------------------------------------------
// snapshot builder + stale-while-revalidate cache
// ---------------------------------------------------------------------------
-let SNAP = { builtAt: null, building: false };
-async function buildSnapshot() {
- if (SNAP.building) return SNAP;
- SNAP.building = true;
+let SNAP = { builtAt: null };
+let BUILDING = null; // in-flight build promise — callers coalesce onto it so an
+ // awaited /api/refresh (or a post-write rebuild) always
+ // returns FRESH data, never a stale in-flight snapshot.
+async function doBuild() {
const wrap = async (fn) => { try { return await fn(); } catch (e) { return { error: e.message }; } };
- try {
- const [system, localFleet, kamatera, canaries, crons] = await Promise.all([
- wrap(collectSystem), wrap(collectLocalFleet), wrap(collectKamateraFleet), wrap(collectCanaries), wrap(collectCrons),
- ]);
- const [sessions, wins, officers] = await Promise.all([
- wrap(() => fetchJSON(`${CNCP}/api/sessions`)),
- wrap(() => fetchJSON(`${CNCP}/api/wins`)),
- wrap(() => fetchJSON(`${CNCP}/api/officers`)),
- ]);
- const cost = wrap(collectCost) instanceof Promise ? await wrap(collectCost) : collectCost();
- const revenue = collectRevenue();
- const pnl = buildPnL(cost, revenue);
- SNAP = {
- builtAt: new Date().toISOString(),
- building: false,
- version: VERSION,
- system, localFleet, kamatera, canaries, crons,
- sessions: summarizeSessions(sessions),
- wins: summarizeWins(wins),
- officers: summarizeOfficers(officers),
- cost, revenue, pnl,
- };
- try { fs.writeFileSync(SNAPSHOT, JSON.stringify(SNAP)); } catch (e) {}
- } finally { SNAP.building = false; }
+ const [system, localFleet, kamatera, canaries, crons] = await Promise.all([
+ wrap(collectSystem), wrap(collectLocalFleet), wrap(collectKamateraFleet), wrap(collectCanaries), wrap(collectCrons),
+ ]);
+ const [sessions, wins, officers] = await Promise.all([
+ wrap(() => fetchJSON(`${CNCP}/api/sessions`)),
+ wrap(() => fetchJSON(`${CNCP}/api/wins`)),
+ wrap(() => fetchJSON(`${CNCP}/api/officers`)),
+ ]);
+ const cost = collectCost();
+ const revenue = collectRevenue();
+ const pnl = buildPnL(cost, revenue);
+ SNAP = {
+ builtAt: new Date().toISOString(),
+ version: VERSION,
+ system, localFleet, kamatera, canaries, crons,
+ sessions: summarizeSessions(sessions),
+ wins: summarizeWins(wins),
+ officers: summarizeOfficers(officers),
+ cost, revenue, pnl,
+ };
+ try { fs.writeFileSync(SNAPSHOT, JSON.stringify(SNAP)); } catch (e) {}
return SNAP;
}
+function buildSnapshot() {
+ if (BUILDING) return BUILDING;
+ BUILDING = doBuild().finally(() => { BUILDING = null; });
+ return BUILDING;
+}
+// force a build that reflects state written AFTER any in-flight build started
+// (used by the money-path write so recorded income shows up immediately).
+async function rebuildFresh() { if (BUILDING) { try { await BUILDING; } catch (e) {} } return buildSnapshot(); }
function summarizeSessions(s) {
const arr = Array.isArray(s) ? s : (s && s.sessions) || [];
return { count: arr.length, recent: arr.slice(0, 12) };
@@ -404,6 +439,22 @@ for (const key of ['system', 'localFleet', 'kamatera', 'canaries', 'crons', 'ses
app.get(`/api/${key}`, (req, res) => res.json(SNAP[key] || {}));
}
+// Record real income against a revenue engine (auth-gated by the middleware
+// above). This is the ONLY money-touching write — it just logs income that
+// actually landed; it does NOT charge anyone. Turning an engine's *mechanism*
+// on (Stripe, ads, affiliate) stays gated to Steve.
+const REVENUE_ENGINES = ['sell_product', 'affiliate', 'billable_work', 'ads'];
+app.post('/api/revenue/record', async (req, res) => {
+ const b = req.body || {};
+ if (!REVENUE_ENGINES.includes(b.engine)) return res.status(400).json({ error: `engine must be one of ${REVENUE_ENGINES.join(', ')}` });
+ const amount = Number(b.amount);
+ if (!isFinite(amount)) return res.status(400).json({ error: 'amount must be a number' });
+ const row = { ts: new Date().toISOString(), engine: b.engine, amount: round(amount, 2), source: (b.source || '').toString().slice(0, 200) };
+ try { fs.appendFileSync(REVENUE_LEDGER, JSON.stringify(row) + '\n'); } catch (e) { return res.status(500).json({ error: 'write failed' }); }
+ await rebuildFresh();
+ res.json({ ok: true, recorded: row, pnl: SNAP.pnl && SNAP.pnl.today });
+});
+
// AI chat — grounded in the current snapshot. Uses local Ollama if reachable
// (unmetered, $0); otherwise returns a deterministic grounded summary. No paid
// API is called from here without Steve wiring it — cost line always shown.
← fd8b534 landed: 7d cost-trend + per-model panel (task 01), energy-at
·
back to AbramsEgo
·
queue: mark tasks 01-03 done (completed by hand during sessi 2c7b504 →