Skip to content

Commit 54ec024

Browse files
feat: cost-per-call transparency + week-over-week activity in ROI dialog (#2)
- Read instance cost_per_call_usd and show 'Cost per avoided call' in ROI Summary - Compute week-over-week activity client-side from the insights 30-day trend - Bump 0.3.2 → 0.3.3 + changelog Co-authored-by: Claude <noreply@anthropic.com>
1 parent b47e115 commit 54ec024

4 files changed

Lines changed: 57 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@
22

33
---
44

5+
## [0.3.3] – 2026-06-13
6+
7+
### Added
8+
- **Cost per avoided call** — the Brain Health dialog's ROI Summary now shows the
9+
per-call price used for savings estimates, read from the instance's configured
10+
`cost_per_call_usd` (falls back to the $0.002 default), so the dollar figures
11+
reflect your real bill.
12+
- **Week-over-week activity** — a new ROI row compares this week's Brain activity
13+
to the prior week (computed client-side from the insights 30-day trend) with a
14+
▲/▼ indicator and percentage change. Shows a "no baseline yet" hint until two
15+
weeks of data exist.
16+
17+
---
18+
519
## [0.3.2] – 2026-06-06
620

721
### Added

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ plugins {
55
}
66

77
group = "dev.cachly"
8-
version = "0.3.2"
8+
version = "0.3.3"
99

1010
repositories {
1111
mavenCentral()

src/main/kotlin/dev/cachly/brain/CachlyApiClient.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ data class MemoryResponse(
4040
data class InstanceResponse(
4141
val tier: String? = null,
4242
val status: String? = null,
43+
@SerializedName("cost_per_call_usd") val costPerCallUsd: Double = 0.0,
44+
)
45+
46+
/** One day of activity from the insights 30-day trend array. */
47+
data class TrendBucket(
48+
val date: String = "",
49+
val events: Int = 0,
50+
val fixes: Int = 0,
4351
)
4452

4553
data class InsightsResponse(
@@ -51,8 +59,23 @@ data class InsightsResponse(
5159
@SerializedName("ttfr_p90_sec") val ttfrP90Sec: Double = -1.0,
5260
val currency: String = "EUR",
5361
@SerializedName("hourly_rate") val hourlyRate: Double = 75.0,
62+
val trend: List<TrendBucket> = emptyList(),
5463
)
5564

65+
/** Week-over-week activity from the trend array: last 7 days vs the prior 7. */
66+
data class WeekOverWeek(val thisWeek: Int, val lastWeek: Int, val pct: Double?)
67+
68+
/** pct is null when there is no prior-week baseline to compare against. */
69+
fun computeWoW(trend: List<TrendBucket>): WeekOverWeek {
70+
if (trend.isEmpty()) return WeekOverWeek(0, 0, null)
71+
val sorted = trend.sortedBy { it.date }
72+
val thisWeek = sorted.takeLast(7).sumOf { it.events }
73+
val prior = sorted.dropLast(7).takeLast(7)
74+
val lastWeek = prior.sumOf { it.events }
75+
val pct = if (lastWeek > 0) (thisWeek - lastWeek).toDouble() / lastWeek * 100 else null
76+
return WeekOverWeek(thisWeek, lastWeek, pct)
77+
}
78+
5679
data class BrainHealth(
5780
val lessons: Int = 0,
5881
val contexts: Int = 0,
@@ -75,6 +98,8 @@ data class BrainHealth(
7598
val recallLimit: Int = -1,
7699
/** ROI aggregates from /api/v1/insights — null if endpoint unavailable. */
77100
val insights: InsightsResponse? = null,
101+
/** Configured per-avoided-call price for ROI; 0 = use the $0.002 default. */
102+
val costPerCallUsd: Double = 0.0,
78103
) {
79104
companion object {
80105
/** Average tokens saved per recall — reuses known solution instead of re-researching. */
@@ -133,6 +158,7 @@ object CachlyApiClient {
133158
pendingLessons = pendingCount,
134159
recallLimit = mem.recallLimit,
135160
insights = insights,
161+
costPerCallUsd = inst.costPerCallUsd,
136162
)
137163
}
138164

src/main/kotlin/dev/cachly/brain/ShowBrainHealthAction.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,29 @@ private class BrainHealthDialog(
8282
val insightsHtml = health.insights?.let { ins ->
8383
val curr = if (ins.currency == "EUR") "" else ins.currency
8484
val ttfr = if (ins.ttfrP50Sec > 0) "${"%.0f".format(ins.ttfrP50Sec)}s" else ""
85+
// Cost-per-avoided-call: prefer the instance's configured price, else the $0.002 default.
86+
val cpc = if (health.costPerCallUsd > 0) health.costPerCallUsd else 0.002
87+
val cpcNote = if (health.costPerCallUsd > 0) "your configured rate" else "default — set it on the instance page"
88+
// Week-over-week activity from the 30-day trend array.
89+
val wow = computeWoW(ins.trend)
90+
val wowPct = wow.pct
91+
val wowRow = if (wowPct == null) {
92+
"<tr><td><b>This week's activity:</b></td><td><b>${wow.thisWeek}</b> events <i>(no prior-week baseline yet)</i></td></tr>"
93+
} else {
94+
val arrow = if (wowPct > 0) "" else if (wowPct < 0) "" else ""
95+
val color = if (wowPct > 0) "#3fb950" else if (wowPct < 0) "#f85149" else "inherit"
96+
val sign = if (wowPct > 0) "+" else ""
97+
"<tr><td><b>Week-over-week activity:</b></td><td><b>${wow.thisWeek}</b> vs ${wow.lastWeek} last week &nbsp;<font color='$color'>$arrow $sign${"%.0f".format(wowPct)}%</font></td></tr>"
98+
}
8599
"""
86100
<h2>💰 ROI Summary</h2>
87101
<table cellpadding="4">
88102
<tr><td><b>Developer time saved:</b></td><td><b>${"%.0f".format(ins.minutesSaved)} min</b></td></tr>
89103
<tr><td><b>Cost saved:</b></td><td><b>$curr${"%.2f".format(ins.dollarsSaved)}</b> <i>(at $curr${ins.hourlyRate}/h)</i></td></tr>
104+
<tr><td><b>Cost per avoided call:</b></td><td><b>$$cpc</b> <i>($cpcNote)</i></td></tr>
90105
<tr><td><b>Knowledge reuse:</b></td><td><b>${"%.1f".format(ins.reusePct)}%</b> of recalls cross-author</td></tr>
91106
<tr><td><b>Time-to-first-recall (p50):</b></td><td>$ttfr</td></tr>
107+
$wowRow
92108
</table>
93109
"""
94110
} ?: ""

0 commit comments

Comments
 (0)