claude-roi 0.8.0 → 0.8.1
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/package.json +1 -1
- package/src/dashboard.html +47 -14
- package/src/metrics.js +43 -1
package/package.json
CHANGED
package/src/dashboard.html
CHANGED
|
@@ -297,7 +297,7 @@
|
|
|
297
297
|
/* Hero Stats — 3+1 layout */
|
|
298
298
|
.hero-stats {
|
|
299
299
|
display: grid;
|
|
300
|
-
grid-template-columns: 1fr
|
|
300
|
+
grid-template-columns: 1fr 240px;
|
|
301
301
|
gap: 20px;
|
|
302
302
|
align-items: stretch;
|
|
303
303
|
}
|
|
@@ -394,24 +394,47 @@
|
|
|
394
394
|
align-items: center;
|
|
395
395
|
justify-content: center;
|
|
396
396
|
text-align: center;
|
|
397
|
-
padding:
|
|
397
|
+
padding: 16px 16px 14px;
|
|
398
|
+
gap: 4px;
|
|
398
399
|
}
|
|
399
400
|
.stat-card.grade .value {
|
|
400
401
|
font-family: var(--font-display);
|
|
401
|
-
font-size:
|
|
402
|
+
font-size: 2rem;
|
|
402
403
|
line-height: 1;
|
|
403
404
|
font-weight: 800;
|
|
404
405
|
}
|
|
406
|
+
.stat-card.grade .label {
|
|
407
|
+
margin-bottom: 2px;
|
|
408
|
+
}
|
|
409
|
+
.stat-card.grade .sub {
|
|
410
|
+
margin: 0;
|
|
411
|
+
}
|
|
405
412
|
.grade-circle {
|
|
406
|
-
width:
|
|
407
|
-
height:
|
|
413
|
+
width: 80px;
|
|
414
|
+
height: 80px;
|
|
408
415
|
border-radius: 50%;
|
|
409
416
|
display: flex;
|
|
410
417
|
align-items: center;
|
|
411
418
|
justify-content: center;
|
|
412
|
-
margin-bottom:
|
|
419
|
+
margin-bottom: 2px;
|
|
413
420
|
position: relative;
|
|
414
421
|
}
|
|
422
|
+
.score-tier {
|
|
423
|
+
font-size: 0.75rem;
|
|
424
|
+
text-transform: uppercase;
|
|
425
|
+
letter-spacing: 0.1em;
|
|
426
|
+
font-weight: 700;
|
|
427
|
+
}
|
|
428
|
+
.score-context {
|
|
429
|
+
font-size: 0.68rem;
|
|
430
|
+
color: var(--text-secondary);
|
|
431
|
+
}
|
|
432
|
+
.score-tip {
|
|
433
|
+
font-size: 0.64rem;
|
|
434
|
+
color: var(--text-muted);
|
|
435
|
+
font-style: italic;
|
|
436
|
+
line-height: 1.3;
|
|
437
|
+
}
|
|
415
438
|
.grade-circle::before {
|
|
416
439
|
content: '';
|
|
417
440
|
position: absolute;
|
|
@@ -1874,6 +1897,13 @@
|
|
|
1874
1897
|
<script>
|
|
1875
1898
|
const GRADE_VAR = { A: 'var(--grade-a)', B: 'var(--grade-b)', C: 'var(--grade-c)', D: 'var(--grade-d)', F: 'var(--grade-f)' };
|
|
1876
1899
|
const GRADE_BG_VAR = { A: 'var(--grade-a-bg)', B: 'var(--grade-b-bg)', C: 'var(--grade-c-bg)', D: 'var(--grade-d-bg)', F: 'var(--grade-f-bg)' };
|
|
1900
|
+
function scoreColor(score) {
|
|
1901
|
+
if (score >= 80) return 'var(--grade-a)';
|
|
1902
|
+
if (score >= 60) return 'var(--grade-b)';
|
|
1903
|
+
if (score >= 40) return 'var(--grade-c)';
|
|
1904
|
+
if (score >= 20) return 'var(--grade-d)';
|
|
1905
|
+
return 'var(--text-muted)';
|
|
1906
|
+
}
|
|
1877
1907
|
const INSIGHT_ICONS = { warning: '!', success: '+', info: 'i', tip: '*' };
|
|
1878
1908
|
const DAY_LABELS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
1879
1909
|
let DATA = null;
|
|
@@ -2081,11 +2111,11 @@ function render() {
|
|
|
2081
2111
|
}
|
|
2082
2112
|
|
|
2083
2113
|
function renderHeroStats(s) {
|
|
2084
|
-
const
|
|
2085
|
-
const
|
|
2086
|
-
const
|
|
2114
|
+
const es = s.efficiencyScore || { score: 0, tier: 'Getting Started', letter: 'F', explanation: '', tip: '' };
|
|
2115
|
+
const sColor = scoreColor(es.score);
|
|
2116
|
+
const scoreDeg = Math.round(es.score * 3.6);
|
|
2087
2117
|
return `<div class="stats-section">
|
|
2088
|
-
<h2>Performance Overview <i class="info-tip" data-tip="High-level metrics for your AI coding sessions — cost, output, efficiency, and overall
|
|
2118
|
+
<h2>Performance Overview <i class="info-tip" data-tip="High-level metrics for your AI coding sessions — cost, output, efficiency, and overall score.">i</i></h2>
|
|
2089
2119
|
<div class="hero-stats">
|
|
2090
2120
|
<div class="hero-stats-left">
|
|
2091
2121
|
<div class="stat-card glow cost-card">
|
|
@@ -2119,11 +2149,14 @@ function renderHeroStats(s) {
|
|
|
2119
2149
|
</div>
|
|
2120
2150
|
</div>
|
|
2121
2151
|
</div>
|
|
2122
|
-
<div class="stat-card grade glow grade-card" style="--grade-color: ${
|
|
2123
|
-
<div class="label">
|
|
2152
|
+
<div class="stat-card grade glow grade-card" style="--grade-color: ${sColor}; --grade-deg: ${scoreDeg}deg;">
|
|
2153
|
+
<div class="label">Efficiency Score <i class="info-tip" data-tip="Score from 0–100 based on cost-per-commit and code survival rate. Higher is better.">i</i></div>
|
|
2124
2154
|
<div class="grade-circle">
|
|
2125
|
-
<div class="value" style="color: ${
|
|
2155
|
+
<div class="value" style="color: ${sColor};">${es.score}</div>
|
|
2126
2156
|
</div>
|
|
2157
|
+
<div class="score-tier" style="color: ${sColor};">${es.tier}</div>
|
|
2158
|
+
<div class="score-context">${es.explanation}</div>
|
|
2159
|
+
<div class="score-tip">${es.tip}</div>
|
|
2127
2160
|
<div class="sub">${s.orphanedSessionRate}% sessions orphaned</div>
|
|
2128
2161
|
</div>
|
|
2129
2162
|
</div>
|
|
@@ -2131,7 +2164,7 @@ function renderHeroStats(s) {
|
|
|
2131
2164
|
<span><span class="dot" style="background: var(--accent-orange);"></span>Cost</span>
|
|
2132
2165
|
<span><span class="dot" style="background: var(--accent-green);"></span>Output</span>
|
|
2133
2166
|
<span><span class="dot" style="background: var(--accent-blue);"></span>Efficiency</span>
|
|
2134
|
-
<span><span class="dot" style="background: ${
|
|
2167
|
+
<span><span class="dot" style="background: ${sColor};"></span>Score: ${es.score} · ${es.tier}</span>
|
|
2135
2168
|
</div>
|
|
2136
2169
|
</div>`;
|
|
2137
2170
|
}
|
package/src/metrics.js
CHANGED
|
@@ -262,6 +262,45 @@ function computeEfficiencyGrade(costPerCommit, survivalRate) {
|
|
|
262
262
|
return 'F';
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
+
function computeEfficiencyScore(costPerCommit, survivalRate, orphanedRate, totalCommits) {
|
|
266
|
+
if (totalCommits === 0) {
|
|
267
|
+
return {
|
|
268
|
+
score: 0, tier: 'Getting Started', letter: 'F',
|
|
269
|
+
explanation: 'No commits matched to sessions yet — this is normal for exploratory work.',
|
|
270
|
+
tip: 'Commits are matched by file overlap with Claude-edited files.',
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Score: 50 pts from cost efficiency (log scale) + 50 pts from survival rate
|
|
275
|
+
let costScore;
|
|
276
|
+
if (costPerCommit <= 2) costScore = 50;
|
|
277
|
+
else if (costPerCommit >= 50) costScore = 0;
|
|
278
|
+
else costScore = Math.max(0, 50 * (1 - Math.log(costPerCommit / 2) / Math.log(25)));
|
|
279
|
+
const survivalScore = Math.min(survivalRate, 100) / 100 * 50;
|
|
280
|
+
const score = Math.round(costScore + survivalScore);
|
|
281
|
+
|
|
282
|
+
const tier = score >= 80 ? 'Excellent' : score >= 60 ? 'Solid' : score >= 40 ? 'Developing' : score >= 20 ? 'Early' : 'Getting Started';
|
|
283
|
+
const letter = score >= 80 ? 'A' : score >= 60 ? 'B' : score >= 40 ? 'C' : score >= 20 ? 'D' : 'F';
|
|
284
|
+
|
|
285
|
+
// Build explanation from actual metrics
|
|
286
|
+
const costAdj = costPerCommit <= 2 ? 'excellent' : costPerCommit <= 5 ? 'good' : costPerCommit <= 15 ? 'moderate' : 'high';
|
|
287
|
+
const explanation = `$${costPerCommit.toFixed(2)}/commit (${costAdj}) · ${Math.round(survivalRate)}% code survival`;
|
|
288
|
+
|
|
289
|
+
// Actionable tip based on weakest metric
|
|
290
|
+
let tip;
|
|
291
|
+
if (costScore < survivalScore) {
|
|
292
|
+
tip = 'Try shorter, focused sessions to reduce cost per commit.';
|
|
293
|
+
} else if (survivalRate < 50) {
|
|
294
|
+
tip = 'Review AI-generated code before committing to improve survival rate.';
|
|
295
|
+
} else if (orphanedRate > 40) {
|
|
296
|
+
tip = `${orphanedRate}% of sessions had no commits — some may be exploratory, which is fine.`;
|
|
297
|
+
} else {
|
|
298
|
+
tip = 'Keep it up — your efficiency is on track.';
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return { score, tier, letter, explanation, tip };
|
|
302
|
+
}
|
|
303
|
+
|
|
265
304
|
function computeSessionGrade(session) {
|
|
266
305
|
if (session.commitCount === 0) return 'F';
|
|
267
306
|
const costPerCommit = session.cost.totalCost / session.commitCount;
|
|
@@ -508,9 +547,11 @@ export function computeMetrics(correlatedSessions, organicCommits, commitsByRepo
|
|
|
508
547
|
const lineSurvival = computeLineSurvival(commitsByRepo);
|
|
509
548
|
|
|
510
549
|
const avgCost = totalCommits > 0 ? totalCost / totalCommits : 0;
|
|
550
|
+
const orphanedSessionRate = totalSessions > 0 ? Math.round((orphanedCount / totalSessions) * 100) : 0;
|
|
511
551
|
const overallGrade = totalCommits > 0
|
|
512
552
|
? computeEfficiencyGrade(avgCost, lineSurvival.survivalRate)
|
|
513
553
|
: 'F';
|
|
554
|
+
const efficiencyScore = computeEfficiencyScore(avgCost, lineSurvival.survivalRate, orphanedSessionRate, totalCommits);
|
|
514
555
|
|
|
515
556
|
// ---- Daily timeline ----
|
|
516
557
|
const dailyMap = new Map();
|
|
@@ -679,9 +720,10 @@ export function computeMetrics(correlatedSessions, organicCommits, commitsByRepo
|
|
|
679
720
|
avgCostPerLine: totalLinesAdded > 0 ? totalCost / totalLinesAdded : null,
|
|
680
721
|
totalInputTokens,
|
|
681
722
|
totalOutputTokens,
|
|
682
|
-
orphanedSessionRate
|
|
723
|
+
orphanedSessionRate,
|
|
683
724
|
lineSurvivalRate: lineSurvival.survivalRate,
|
|
684
725
|
overallGrade,
|
|
726
|
+
efficiencyScore,
|
|
685
727
|
totalCommitsOnMain,
|
|
686
728
|
mainBranchPct: totalCommits > 0 ? Math.round((totalCommitsOnMain / totalCommits) * 100) : 0,
|
|
687
729
|
organicCommitCount: organicCommits.length,
|