claude-roi 0.8.7 → 0.8.8

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/dashboard.html +127 -17
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-roi",
3
- "version": "0.8.7",
3
+ "version": "0.8.8",
4
4
  "description": "Correlate AI coding agent token usage with git output to measure ROI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2245,6 +2245,8 @@ let sortCol = 'startTime';
2245
2245
  let sortOrder = -1;
2246
2246
  let timelineChart = null;
2247
2247
  let timelineLogScale = false;
2248
+ let tokenBurnChart = null;
2249
+ let tokenBurnPercentMode = false;
2248
2250
 
2249
2251
  document.addEventListener('DOMContentLoaded', async () => {
2250
2252
  initTheme();
@@ -2308,6 +2310,7 @@ function render() {
2308
2310
  <div class="chart-card full-width">
2309
2311
  <div class="chart-header">
2310
2312
  <h3>Token Burn Rate <i class="info-tip" data-tip="Daily token consumption stacked by type (input/output/cache). Dashed orange line shows cumulative total — watch it climb.">i</i></h3>
2313
+ <button class="scale-toggle" onclick="toggleTokenBurnPercent()">Split view</button>
2311
2314
  </div>
2312
2315
  <div class="chart-container"><canvas id="chart-token-burn"></canvas></div>
2313
2316
  </div>
@@ -2327,8 +2330,8 @@ function render() {
2327
2330
  <div class="chart-container"><canvas id="chart-tools"></canvas></div>
2328
2331
  </div>
2329
2332
  <div class="chart-card">
2330
- <h3>Session Length vs Efficiency <i class="info-tip" data-tip="Sessions grouped by message count (1-50, 51-100, etc). Blue bars = average cost per commit for that group. Purple bars = number of sessions. Find your sweet spot the group with the lowest blue bar.">i</i></h3>
2331
- <div class="chart-container"><canvas id="chart-buckets"></canvas></div>
2333
+ <h3>Cost per Commit by Model <i class="info-tip" data-tip="Compare how much each model costs per commit. Lower blue bars = better value. Purple bars show commit volume for confidence. Use this to decide which model to use for different tasks.">i</i></h3>
2334
+ <div class="chart-container"><canvas id="chart-model-efficiency"></canvas></div>
2332
2335
  </div>
2333
2336
  <div class="chart-card">
2334
2337
  <h3>Productivity Heatmap <i class="info-tip" data-tip="Each cell = commits produced during that hour and day of week. Darker green = more commits. Find your peak productivity windows.">i</i></h3>
@@ -2891,7 +2894,7 @@ function initCharts() {
2891
2894
  let cumulative = 0;
2892
2895
  const cumulativeData = dailyTokens.map(d => { cumulative += (d.totalTokens || 0); return cumulative; });
2893
2896
 
2894
- new Chart(document.getElementById('chart-token-burn'), {
2897
+ tokenBurnChart = new Chart(document.getElementById('chart-token-burn'), {
2895
2898
  type: 'bar',
2896
2899
  data: {
2897
2900
  labels: dailyTokens.map(d => new Date(d.date + 'T12:00:00').toLocaleDateString(undefined, { month: 'short', day: 'numeric' })),
@@ -3123,25 +3126,29 @@ function initCharts() {
3123
3126
  },
3124
3127
  });
3125
3128
 
3126
- // Session buckets
3127
- const buckets = DATA.sessionBuckets;
3128
- const bucketLabels = Object.keys(buckets);
3129
- new Chart(document.getElementById('chart-buckets'), {
3129
+ // Cost per Commit by Model
3130
+ const modelEff = DATA.modelBreakdown;
3131
+ const modelEffLabels = Object.keys(modelEff).filter(k => modelEff[k].commits > 0);
3132
+ const modelEffColors = { opus: '#a855f7', sonnet: '#3b82f6', haiku: '#22d3a8', unknown: '#94a3b8' };
3133
+ new Chart(document.getElementById('chart-model-efficiency'), {
3130
3134
  type: 'bar',
3131
3135
  data: {
3132
- labels: bucketLabels,
3136
+ labels: modelEffLabels.map(m => m.charAt(0).toUpperCase() + m.slice(1) + ' ($' + modelEff[m].cost.toFixed(0) + ' total)'),
3133
3137
  datasets: [
3134
3138
  {
3135
3139
  label: 'Avg $/Commit',
3136
- data: bucketLabels.map(b => buckets[b].avgCostPerCommit),
3137
- backgroundColor: '#3b82f6',
3140
+ data: modelEffLabels.map(m => modelEff[m].avgCostPerCommit),
3141
+ backgroundColor: modelEffLabels.map(m => modelEffColors[m] || '#94a3b8'),
3138
3142
  borderRadius: 4,
3139
3143
  yAxisID: 'y',
3140
3144
  },
3141
3145
  {
3142
- label: 'Sessions',
3143
- data: bucketLabels.map(b => buckets[b].sessions),
3144
- backgroundColor: 'rgba(168,85,247,0.4)',
3146
+ label: 'Commits',
3147
+ data: modelEffLabels.map(m => Math.round(modelEff[m].commits)),
3148
+ backgroundColor: modelEffLabels.map(m => {
3149
+ const c = modelEffColors[m] || '#94a3b8';
3150
+ return c + '40';
3151
+ }),
3145
3152
  borderRadius: 4,
3146
3153
  yAxisID: 'y1',
3147
3154
  },
@@ -3150,10 +3157,30 @@ function initCharts() {
3150
3157
  options: {
3151
3158
  responsive: true,
3152
3159
  maintainAspectRatio: false,
3153
- plugins: { legend: { position: 'top' } },
3160
+ plugins: {
3161
+ legend: { position: 'top' },
3162
+ tooltip: {
3163
+ callbacks: {
3164
+ label: ctx => {
3165
+ if (ctx.datasetIndex === 0) return ` Avg $/Commit: $${ctx.parsed.y.toFixed(2)}`;
3166
+ return ` Commits: ${ctx.parsed.y}`;
3167
+ },
3168
+ afterBody: (items) => {
3169
+ const idx = items[0].dataIndex;
3170
+ const m = modelEffLabels[idx];
3171
+ const d = modelEff[m];
3172
+ const lines = [];
3173
+ lines.push(`Sessions: ${d.sessions}`);
3174
+ lines.push(`Total: $${d.cost.toFixed(2)} | ${formatTokens(d.tokens)} tokens`);
3175
+ if (d.tokensPerCommit) lines.push(`${formatTokens(d.tokensPerCommit)} tokens/commit`);
3176
+ return lines;
3177
+ },
3178
+ },
3179
+ },
3180
+ },
3154
3181
  scales: {
3155
- y: { type: 'linear', position: 'left', title: { display: true, text: 'Avg $/Commit' } },
3156
- y1: { type: 'linear', position: 'right', title: { display: true, text: 'Sessions' }, grid: { drawOnChartArea: false } },
3182
+ y: { type: 'linear', position: 'left', title: { display: true, text: 'Avg $/Commit' }, ticks: { callback: v => '$' + v.toFixed(1) } },
3183
+ y1: { type: 'linear', position: 'right', title: { display: true, text: 'Commits' }, grid: { drawOnChartArea: false } },
3157
3184
  },
3158
3185
  },
3159
3186
  });
@@ -3263,10 +3290,93 @@ window.toggleTimelineScale = function() {
3263
3290
  timelineChart.options.scales.y.type = scaleType;
3264
3291
  timelineChart.options.scales.y1.type = scaleType;
3265
3292
  timelineChart.update();
3266
- const btn = document.querySelector('.scale-toggle');
3293
+ const btn = document.querySelector('#chart-timeline').closest('.chart-card').querySelector('.scale-toggle');
3267
3294
  if (btn) btn.textContent = timelineLogScale ? 'Linear scale' : 'Log scale';
3268
3295
  };
3269
3296
 
3297
+ window.toggleTokenBurnPercent = function() {
3298
+ if (!tokenBurnChart) return;
3299
+ tokenBurnPercentMode = !tokenBurnPercentMode;
3300
+ const dailyTokens = DATA.daily;
3301
+ if (tokenBurnPercentMode) {
3302
+ // Switch to split line view — each token type gets its own line
3303
+ tokenBurnChart.data.datasets[0].type = 'line';
3304
+ tokenBurnChart.data.datasets[0].borderColor = 'rgba(59, 130, 246, 1)';
3305
+ tokenBurnChart.data.datasets[0].backgroundColor = 'rgba(59, 130, 246, 0.1)';
3306
+ tokenBurnChart.data.datasets[0].fill = true;
3307
+ tokenBurnChart.data.datasets[0].stack = undefined;
3308
+ tokenBurnChart.data.datasets[0].borderWidth = 2;
3309
+ tokenBurnChart.data.datasets[0].pointRadius = 2;
3310
+ tokenBurnChart.data.datasets[0].tension = 0.3;
3311
+
3312
+ tokenBurnChart.data.datasets[1].type = 'line';
3313
+ tokenBurnChart.data.datasets[1].borderColor = 'rgba(168, 85, 247, 1)';
3314
+ tokenBurnChart.data.datasets[1].backgroundColor = 'rgba(168, 85, 247, 0.1)';
3315
+ tokenBurnChart.data.datasets[1].fill = true;
3316
+ tokenBurnChart.data.datasets[1].stack = undefined;
3317
+ tokenBurnChart.data.datasets[1].borderWidth = 2;
3318
+ tokenBurnChart.data.datasets[1].pointRadius = 2;
3319
+ tokenBurnChart.data.datasets[1].tension = 0.3;
3320
+
3321
+ tokenBurnChart.data.datasets[2].type = 'line';
3322
+ tokenBurnChart.data.datasets[2].borderColor = 'rgba(6, 182, 212, 1)';
3323
+ tokenBurnChart.data.datasets[2].backgroundColor = 'rgba(6, 182, 212, 0.1)';
3324
+ tokenBurnChart.data.datasets[2].fill = true;
3325
+ tokenBurnChart.data.datasets[2].stack = undefined;
3326
+ tokenBurnChart.data.datasets[2].borderWidth = 2;
3327
+ tokenBurnChart.data.datasets[2].pointRadius = 2;
3328
+ tokenBurnChart.data.datasets[2].tension = 0.3;
3329
+
3330
+ tokenBurnChart.data.datasets[3].hidden = true;
3331
+ tokenBurnChart.options.scales.x.stacked = false;
3332
+ tokenBurnChart.options.scales.y.stacked = false;
3333
+ tokenBurnChart.options.scales.y.type = 'logarithmic';
3334
+ tokenBurnChart.options.scales.y.title.text = 'Tokens / Day (log)';
3335
+ tokenBurnChart.options.scales.y1.display = false;
3336
+ } else {
3337
+ // Restore stacked bar view
3338
+ tokenBurnChart.data.datasets[0].type = 'bar';
3339
+ tokenBurnChart.data.datasets[0].backgroundColor = 'rgba(59, 130, 246, 0.8)';
3340
+ tokenBurnChart.data.datasets[0].stack = 'tokens';
3341
+ tokenBurnChart.data.datasets[0].borderRadius = 2;
3342
+ delete tokenBurnChart.data.datasets[0].borderColor;
3343
+ delete tokenBurnChart.data.datasets[0].fill;
3344
+ delete tokenBurnChart.data.datasets[0].borderWidth;
3345
+ delete tokenBurnChart.data.datasets[0].pointRadius;
3346
+ delete tokenBurnChart.data.datasets[0].tension;
3347
+
3348
+ tokenBurnChart.data.datasets[1].type = 'bar';
3349
+ tokenBurnChart.data.datasets[1].backgroundColor = 'rgba(168, 85, 247, 0.8)';
3350
+ tokenBurnChart.data.datasets[1].stack = 'tokens';
3351
+ tokenBurnChart.data.datasets[1].borderRadius = 2;
3352
+ delete tokenBurnChart.data.datasets[1].borderColor;
3353
+ delete tokenBurnChart.data.datasets[1].fill;
3354
+ delete tokenBurnChart.data.datasets[1].borderWidth;
3355
+ delete tokenBurnChart.data.datasets[1].pointRadius;
3356
+ delete tokenBurnChart.data.datasets[1].tension;
3357
+
3358
+ tokenBurnChart.data.datasets[2].type = 'bar';
3359
+ tokenBurnChart.data.datasets[2].backgroundColor = 'rgba(6, 182, 212, 0.6)';
3360
+ tokenBurnChart.data.datasets[2].stack = 'tokens';
3361
+ tokenBurnChart.data.datasets[2].borderRadius = 2;
3362
+ delete tokenBurnChart.data.datasets[2].borderColor;
3363
+ delete tokenBurnChart.data.datasets[2].fill;
3364
+ delete tokenBurnChart.data.datasets[2].borderWidth;
3365
+ delete tokenBurnChart.data.datasets[2].pointRadius;
3366
+ delete tokenBurnChart.data.datasets[2].tension;
3367
+
3368
+ tokenBurnChart.data.datasets[3].hidden = false;
3369
+ tokenBurnChart.options.scales.x.stacked = true;
3370
+ tokenBurnChart.options.scales.y.stacked = true;
3371
+ tokenBurnChart.options.scales.y.type = 'linear';
3372
+ tokenBurnChart.options.scales.y.title.text = 'Tokens / Day';
3373
+ tokenBurnChart.options.scales.y1.display = true;
3374
+ }
3375
+ tokenBurnChart.update();
3376
+ const btn = document.querySelector('#chart-token-burn').closest('.chart-card').querySelector('.scale-toggle');
3377
+ if (btn) btn.textContent = tokenBurnPercentMode ? 'Stacked' : 'Split view';
3378
+ };
3379
+
3270
3380
  /* ── Share Report Card ─────────────────────────── */
3271
3381
 
3272
3382
  const GRADE_HEX = { A: '#22d3a8', B: '#3b82f6', C: '#f59e0b', D: '#f0883e', F: '#ef4444' };