← back to AbramsEgo
feat: energy attribution + break-even target in P&L
42aceb2fd7b17da338669f784455da950031f3b2 · 2026-07-01 10:45:58 -0700 · Steve (AbramsEgo)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Files touched
M public/index.htmlM server.js
Diff
commit 42aceb2fd7b17da338669f784455da950031f3b2
Author: Steve (AbramsEgo) <steve@designerwallcoverings.com>
Date: Wed Jul 1 10:45:58 2026 -0700
feat: energy attribution + break-even target in P&L
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
public/index.html | 11 +++++++++++
server.js | 20 +++++++++++++++++---
2 files changed, 28 insertions(+), 3 deletions(-)
diff --git a/public/index.html b/public/index.html
index 12012ad..5a80e6e 100644
--- a/public/index.html
+++ b/public/index.html
@@ -78,6 +78,11 @@
<div class="stat"><div class="k">Net</div><div class="v" id="pnl-net">—</div><div class="muted">revenue − cost</div></div>
<div class="stat"><div class="k">Self-funding</div><div class="v" id="pnl-pct">—</div><div class="muted">of run cost covered</div></div>
</div>
+ <div style="margin-top:14px">
+ <div class="muted" id="pnl-breakeven">Break-even: —</div>
+ <div class="bar" title="progress toward self-funding (100%)"><span id="pnl-progress" style="width:0%"></span></div>
+ <div class="muted" id="pnl-runway" style="margin-top:6px"></div>
+ </div>
<div style="margin-top:12px" id="engines"></div>
</section>
@@ -176,6 +181,12 @@ async function load(){
$('pnl-net').className='v '+((p.net||0)>=0?'good':'bad');
$('pnl-pct').textContent=(p.selfFundingPct==null?'0':p.selfFundingPct)+'%';
$('pnl-pct').className='v '+((p.selfFundingPct||0)>=100?'good':(p.selfFundingPct||0)>0?'warn':'bad');
+ // break-even target + progress toward self-funding (100%)
+ const pn=d.pnl||{}, pct=p.selfFundingPct||0;
+ const col=pct>=100?'var(--good)':pct<50?'var(--bad)':'var(--warn)';
+ $('pnl-breakeven').innerHTML=`Break-even: <b style="color:var(--gold)">${usd(pn.breakEvenUsdPerDay)}/day</b> · <b style="color:var(--gold)">${usd(pn.breakEvenUsdPerMonth)}/mo</b>`;
+ const bar=$('pnl-progress'); bar.style.width=Math.min(100,pct)+'%'; bar.style.background=col;
+ $('pnl-runway').textContent=pn.runwayNote||'';
const engs=(d.revenue&&d.revenue.engines)||[];
$('engines').innerHTML = engs.map(e=>`<div class="eng"><span>${e.label} <span class="muted">${e.note}</span></span>
<span class="pill ${e.status==='live'?'live':'gated'}">${e.status==='live'?usd(e.amount):'GATED'}</span></div>`).join('');
diff --git a/server.js b/server.js
index e16f2f3..e161591 100644
--- a/server.js
+++ b/server.js
@@ -36,6 +36,9 @@ const START_TS = Date.now();
// --- self-funding knobs (env-tunable, all estimates clearly labeled) ---------
const ENERGY_AVG_WATTS = parseFloat(process.env.ENERGY_AVG_WATTS || '90'); // Mac Studio ~30W idle → ~140W load
const ENERGY_RATE = parseFloat(process.env.ENERGY_RATE_USD_PER_KWH || '0.30'); // CA residential ~$0.30/kWh
+// fraction (0–100) of machine draw attributed to AbramsEgo/Claude — the box runs
+// other work too, so 100 = charge all of it here, e.g. 40 = charge 40% to us.
+const ENERGY_ATTRIB = Math.min(1, Math.max(0, parseFloat(process.env.ENERGY_ATTRIB_PCT || '100') / 100));
const CNCP = process.env.CNCP_BASE || 'http://127.0.0.1:3333';
const KAMATERA = process.env.KAMATERA_HOST || '45.61.58.125';
const COST_LEDGER = path.join(HOME, '.claude', 'cost-ledger.jsonl');
@@ -210,7 +213,7 @@ function readCostLedger(sinceMs) {
function energyCost(sinceMs) {
const hours = (Date.now() - sinceMs) / 3.6e6;
const kwh = (ENERGY_AVG_WATTS / 1000) * hours;
- return { hours: round(hours, 1), kwh: round(kwh, 3), usd: round(kwh * ENERGY_RATE, 4), est: true, avgWatts: ENERGY_AVG_WATTS, rate: ENERGY_RATE };
+ return { hours: round(hours, 1), kwh: round(kwh, 3), usd: round(kwh * ENERGY_RATE * ENERGY_ATTRIB, 4), est: true, avgWatts: ENERGY_AVG_WATTS, rate: ENERGY_RATE, attribPct: round(ENERGY_ATTRIB * 100, 1) };
}
// 7-day daily buckets of cost (tokens+energy) + a per-api/model breakdown for
// 30d. One pass over the ledger keeps this cheap. Days are keyed by LOCAL
@@ -248,10 +251,10 @@ function collectCostSeries() {
}
} catch (e) {}
// energy per day: a full 24h for past days, partial (midnight→now) for today
- const perDayFullUsd = round((ENERGY_AVG_WATTS / 1000) * 24 * ENERGY_RATE, 4);
+ const perDayFullUsd = round((ENERGY_AVG_WATTS / 1000) * 24 * ENERGY_RATE * ENERGY_ATTRIB, 4);
const sod = new Date(); sod.setHours(0, 0, 0, 0);
const todayHours = (now - sod.getTime()) / 3.6e6;
- const todayUsd = round((ENERGY_AVG_WATTS / 1000) * todayHours * ENERGY_RATE, 4);
+ const todayUsd = round((ENERGY_AVG_WATTS / 1000) * todayHours * ENERGY_RATE * ENERGY_ATTRIB, 4);
const todayKey = dayKey(now);
const series7d = buckets.map((b) => {
const energy = b.day === todayKey ? todayUsd : perDayFullUsd;
@@ -305,6 +308,17 @@ function buildPnL(cost, revenue) {
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 };
}
+ // Break-even target: how much daily/monthly revenue AbramsEgo must earn to pay
+ // for itself. Today's all-in cost is the daily target; ×30 gives the monthly.
+ const dayTarget = (out.today && out.today.cost) || 0;
+ out.breakEvenUsdPerDay = round(dayTarget, 4);
+ out.breakEvenUsdPerMonth = round(dayTarget * 30, 2);
+ const pct = out.today && out.today.selfFundingPct;
+ out.runwayNote = pct == null
+ ? 'No cost logged today yet — break-even target not established.'
+ : pct >= 100
+ ? `Self-funding: today's revenue covers ${pct}% of the $${out.breakEvenUsdPerDay}/day it costs to run. In the black.`
+ : `Break-even needs $${out.breakEvenUsdPerDay}/day ($${out.breakEvenUsdPerMonth}/mo). Currently at ${pct}% — need ${round(100 - pct, 1)} more points to self-fund.`;
return out;
}
← 6623e26 feat: 7d cost trend + per-model breakdown
·
back to AbramsEgo
·
landed: 7d cost-trend + per-model panel (task 01), energy-at fd8b534 →