coderplan-tier 0.2.0 → 0.3.0

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.
@@ -40,6 +40,8 @@ export function recommend(usage) {
40
40
  const apiOnlyCost = projectedCosts.find((c) => c.tier === "API Only")?.monthlyCost ?? currentMonthlyCost;
41
41
  const recommendedCost = projectedCosts.find((c) => c.tier === tier)?.monthlyCost ?? currentMonthlyCost;
42
42
  const savings = apiOnlyCost - recommendedCost;
43
+ // Generate explanation comparing recommended tier to next tier up
44
+ const explanation = generateExplanation(tier, projectedCosts);
43
45
  return {
44
46
  tier,
45
47
  confidence,
@@ -47,6 +49,7 @@ export function recommend(usage) {
47
49
  projectedCosts,
48
50
  currentMonthlyCost,
49
51
  savings: Math.max(0, savings),
52
+ explanation,
50
53
  };
51
54
  }
52
55
  function selectLowestCostTier(projectedCosts) {
@@ -130,3 +133,44 @@ function generateReasons(tier, monthlyCost, activeDays, usage) {
130
133
  }
131
134
  return reasons;
132
135
  }
136
+ const TIER_ORDER = ["API Only", "Pro", "Max 5x", "Max 20x"];
137
+ function generateExplanation(recommendedTier, projectedCosts) {
138
+ const recommendedIdx = TIER_ORDER.indexOf(recommendedTier);
139
+ const recommendedCost = projectedCosts.find((c) => c.tier === recommendedTier);
140
+ if (!recommendedCost)
141
+ return null;
142
+ // Compare to next tier up (or down if at max)
143
+ let comparedTier;
144
+ if (recommendedIdx < TIER_ORDER.length - 1) {
145
+ comparedTier = TIER_ORDER[recommendedIdx + 1];
146
+ }
147
+ else {
148
+ comparedTier = TIER_ORDER[recommendedIdx - 1];
149
+ }
150
+ const comparedCost = projectedCosts.find((c) => c.tier === comparedTier);
151
+ if (!comparedCost)
152
+ return null;
153
+ const subscriptionDiff = comparedCost.subscriptionCost - recommendedCost.subscriptionCost;
154
+ const overflowDiff = recommendedCost.overflowCost - comparedCost.overflowCost;
155
+ // Calculate break-even point
156
+ // At break-even: recSub + recOverflow(x) = compSub + compOverflow(x)
157
+ // recSub + x*(1-recCoverage) = compSub + x*(1-compCoverage)
158
+ // x*(compCoverage - recCoverage) = compSub - recSub
159
+ // x = (compSub - recSub) / (compCoverage - recCoverage)
160
+ const recCoverage = COVERAGE_RATIO[recommendedTier];
161
+ const compCoverage = COVERAGE_RATIO[comparedTier];
162
+ const coverageDiff = compCoverage - recCoverage;
163
+ let breakEvenCost = 0;
164
+ if (coverageDiff > 0) {
165
+ breakEvenCost = Math.round(subscriptionDiff / coverageDiff);
166
+ }
167
+ return {
168
+ recommendedTier,
169
+ comparedTier,
170
+ recommendedCost,
171
+ comparedCost,
172
+ subscriptionDiff,
173
+ overflowDiff,
174
+ breakEvenCost,
175
+ };
176
+ }
package/dist/renderer.js CHANGED
@@ -61,6 +61,11 @@ export function renderRecommendation(usage, rec, verbose) {
61
61
  lines.push(boxContent(line));
62
62
  }
63
63
  lines.push(boxBottom());
64
+ // Why this tier? explanation
65
+ if (rec.explanation) {
66
+ lines.push("");
67
+ lines.push(...renderExplanation(rec.explanation));
68
+ }
64
69
  // Verbose: show detailed metrics
65
70
  if (verbose) {
66
71
  lines.push("");
@@ -96,9 +101,29 @@ export function renderJson(usage, rec) {
96
101
  currentMonthlyCost: rec.currentMonthlyCost,
97
102
  projectedSavings: rec.savings,
98
103
  tierCosts: rec.projectedCosts,
104
+ explanation: rec.explanation,
99
105
  },
100
106
  }, null, 2);
101
107
  }
108
+ function renderExplanation(exp) {
109
+ const lines = [];
110
+ const rec = exp.recommendedCost;
111
+ const comp = exp.comparedCost;
112
+ lines.push(pc.bold(`Why ${exp.recommendedTier} over ${exp.comparedTier}?`));
113
+ lines.push(` ${padRight(exp.recommendedTier + ":", 10)} $${rec.subscriptionCost} subscription + $${rec.overflowCost.toFixed(0)} overflow = ${pc.green(`$${rec.monthlyCost.toFixed(0)} total`)}`);
114
+ lines.push(` ${padRight(exp.comparedTier + ":", 10)} $${comp.subscriptionCost} subscription + $${comp.overflowCost.toFixed(0)} overflow = ${pc.dim(`$${comp.monthlyCost.toFixed(0)} total`)}`);
115
+ lines.push("");
116
+ if (exp.subscriptionDiff > 0 && exp.overflowDiff > 0) {
117
+ lines.push(` The $${exp.subscriptionDiff} subscription difference outweighs the $${exp.overflowDiff.toFixed(0)} overflow savings.`);
118
+ }
119
+ else if (exp.subscriptionDiff < 0) {
120
+ lines.push(` Lower subscription ($${Math.abs(exp.subscriptionDiff)} less) with acceptable overflow.`);
121
+ }
122
+ if (exp.breakEvenCost > 0) {
123
+ lines.push(` ${pc.dim(`${exp.comparedTier} becomes better when API costs reach ~$${exp.breakEvenCost}/month.`)}`);
124
+ }
125
+ return lines;
126
+ }
102
127
  // Box drawing helpers
103
128
  function boxTop() {
104
129
  return pc.dim("╭" + "─".repeat(BOX_WIDTH) + "╮");
package/dist/types.d.ts CHANGED
@@ -33,6 +33,16 @@ export interface Recommendation {
33
33
  projectedCosts: TierCost[];
34
34
  currentMonthlyCost: number;
35
35
  savings: number;
36
+ explanation: TierExplanation | null;
37
+ }
38
+ export interface TierExplanation {
39
+ recommendedTier: Tier;
40
+ comparedTier: Tier;
41
+ recommendedCost: TierCost;
42
+ comparedCost: TierCost;
43
+ subscriptionDiff: number;
44
+ overflowDiff: number;
45
+ breakEvenCost: number;
36
46
  }
37
47
  export interface TierCost {
38
48
  tier: Tier;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coderplan-tier",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Analyze Claude Code usage and get tier recommendations",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",