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.
- package/dist/recommender.js +44 -0
- package/dist/renderer.js +25 -0
- package/dist/types.d.ts +10 -0
- package/package.json +1 -1
package/dist/recommender.js
CHANGED
|
@@ -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;
|