profitable 0.2.1 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +4 -4
- data/lib/profitable/version.rb +1 -1
- data/lib/profitable.rb +57 -21
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 506d907bbcbaa811d4986d49fffdc7f1874c77528fb99996f49621834a15e263
|
4
|
+
data.tar.gz: 6e37aaa616a3238b85cacfd4d3b9373781b4632fb4e4e36ad7946a8d71fa066e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aafa273d6430cd1e4c30dada038bdc83a9ecacec8c2d9be0f9f6bad642dc818399dbb494876244eb50c96c1aa3af7932ef43f56e839ab11a8b8f036a375f822f
|
7
|
+
data.tar.gz: 7dca54330874ae397c77b2400364429509dffc5de96a2096fe963984455ff4c46e67849c5305218086684beb7b1d5300815f49af252ba9e875c028c42b717c1a
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# `profitable`
|
2
2
|
|
3
|
+
## [0.2.3] - 2024-09-01
|
4
|
+
|
5
|
+
- Fix the `time_to_next_mrr_milestone` estimation and make it accurate to the day
|
6
|
+
|
7
|
+
## [0.2.2] - 2024-09-01
|
8
|
+
|
9
|
+
- Improve MRR calculations with prorated churned and new MRR (hopefully fixes bad churned MRR calculations)
|
10
|
+
- Only consider paid charges for all revenue calculations (hopefully fixes bad ARPC calculations)
|
11
|
+
- Add `multiple:` parameter as another option for `estimated_valuation` (same as `at:`, just syntactic sugar)
|
12
|
+
|
3
13
|
## [0.2.1] - 2024-08-31
|
4
14
|
|
5
15
|
- Add syntactic sugar for `estimated_valuation(at: "3x")`
|
data/README.md
CHANGED
@@ -100,11 +100,11 @@ Profitable.churn(in_the_last: 3.months).to_readable # => "12%"
|
|
100
100
|
# You can specify the precision of the output number (no decimals by default)
|
101
101
|
Profitable.new_mrr(in_the_last: 24.hours).to_readable(2) # => "$123.45"
|
102
102
|
|
103
|
-
# Get the estimated valuation at 5x ARR
|
104
|
-
Profitable.estimated_valuation(
|
103
|
+
# Get the estimated valuation at 5x ARR (defaults to 3x if no multiple is specified)
|
104
|
+
Profitable.estimated_valuation(multiple: 5).to_readable # => "$500,000"
|
105
105
|
|
106
|
-
# You can also pass the multiplier as a
|
107
|
-
Profitable.estimated_valuation(4.
|
106
|
+
# You can also pass the multiplier as a string. You can also use the `at:` keyword argument (same thing as `multiplier:`) – and/or ignore the `at:` or `multiplier:` named arguments altogether
|
107
|
+
Profitable.estimated_valuation(at: "4.5x").to_readable # => "$450,000"
|
108
108
|
|
109
109
|
# Get the time to next MRR milestone
|
110
110
|
Profitable.time_to_next_mrr_milestone.to_readable # => "26 days left to $10,000 MRR"
|
data/lib/profitable/version.rb
CHANGED
data/lib/profitable.rb
CHANGED
@@ -16,7 +16,7 @@ module Profitable
|
|
16
16
|
include ActionView::Helpers::NumberHelper
|
17
17
|
|
18
18
|
DEFAULT_PERIOD = 30.days
|
19
|
-
MRR_MILESTONES = [100, 1_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000]
|
19
|
+
MRR_MILESTONES = [5, 10, 20, 30, 50, 75, 100, 200, 300, 400, 500, 1_000, 2_000, 3_000, 5_000, 10_000, 20_000, 30_000, 50_000, 83_333, 100_000, 250_000, 500_000, 1_000_000, 5_000_000, 10_000_000, 25_000_000, 50_000_000, 75_000_000, 100_000_000]
|
20
20
|
|
21
21
|
def mrr
|
22
22
|
NumericResult.new(MrrCalculator.calculate)
|
@@ -46,8 +46,8 @@ module Profitable
|
|
46
46
|
NumericResult.new(calculate_recurring_revenue_percentage(in_the_last), :percentage)
|
47
47
|
end
|
48
48
|
|
49
|
-
def estimated_valuation(multiplier =
|
50
|
-
actual_multiplier = at ||
|
49
|
+
def estimated_valuation(multiplier = nil, at: nil, multiple: nil)
|
50
|
+
actual_multiplier = multiplier || at || multiple || 3
|
51
51
|
NumericResult.new(calculate_estimated_valuation(actual_multiplier))
|
52
52
|
end
|
53
53
|
|
@@ -104,13 +104,18 @@ module Profitable
|
|
104
104
|
next_milestone = MRR_MILESTONES.find { |milestone| milestone > current_mrr }
|
105
105
|
return "Congratulations! You've reached the highest milestone." unless next_milestone
|
106
106
|
|
107
|
-
|
108
|
-
return "Unable to calculate. Need more data or positive growth." if
|
107
|
+
monthly_growth_rate = calculate_mrr_growth_rate / 100
|
108
|
+
return "Unable to calculate. Need more data or positive growth." if monthly_growth_rate <= 0
|
109
109
|
|
110
|
-
|
111
|
-
|
110
|
+
# Convert monthly growth rate to daily growth rate
|
111
|
+
daily_growth_rate = (1 + monthly_growth_rate) ** (1.0 / 30) - 1
|
112
112
|
|
113
|
-
|
113
|
+
# Calculate the number of days to reach the next milestone
|
114
|
+
days_to_milestone = (Math.log(next_milestone.to_f / current_mrr) / Math.log(1 + daily_growth_rate)).ceil
|
115
|
+
|
116
|
+
target_date = Time.current + days_to_milestone.days
|
117
|
+
|
118
|
+
"#{days_to_milestone} days left to $#{number_with_delimiter(next_milestone)} MRR (#{target_date.strftime('%b %d, %Y')})"
|
114
119
|
end
|
115
120
|
|
116
121
|
private
|
@@ -170,23 +175,50 @@ module Profitable
|
|
170
175
|
end
|
171
176
|
|
172
177
|
def calculate_churned_mrr(period = DEFAULT_PERIOD)
|
173
|
-
|
174
|
-
|
175
|
-
|
178
|
+
start_date = period.ago
|
179
|
+
end_date = Time.current
|
180
|
+
|
181
|
+
Pay::Subscription
|
182
|
+
.includes(:customer)
|
183
|
+
.select('pay_subscriptions.*, pay_customers.processor as customer_processor')
|
184
|
+
.joins(:customer)
|
185
|
+
.where(status: ['canceled', 'ended'])
|
186
|
+
.where('pay_subscriptions.updated_at BETWEEN ? AND ?', start_date, end_date)
|
187
|
+
.sum do |subscription|
|
188
|
+
if subscription.ends_at && subscription.ends_at > end_date
|
189
|
+
# Subscription ends in the future, don't count it as churned yet
|
190
|
+
0
|
191
|
+
else
|
192
|
+
# Calculate prorated MRR if the subscription ended within the period
|
193
|
+
end_date = [subscription.ends_at, end_date].compact.min
|
194
|
+
days_in_period = (end_date - start_date).to_i
|
195
|
+
total_days = (subscription.current_period_end - subscription.current_period_start).to_i
|
196
|
+
prorated_days = [days_in_period, total_days].min
|
197
|
+
|
198
|
+
mrr = MrrCalculator.process_subscription(subscription)
|
199
|
+
(mrr.to_f * prorated_days / total_days).round
|
200
|
+
end
|
201
|
+
end
|
176
202
|
end
|
177
203
|
|
178
204
|
def calculate_new_mrr(period = DEFAULT_PERIOD)
|
179
|
-
|
205
|
+
start_date = period.ago
|
206
|
+
end_date = Time.current
|
207
|
+
|
208
|
+
Pay::Subscription
|
180
209
|
.active
|
181
|
-
.where(pay_subscriptions: { created_at: period.ago..Time.current })
|
182
|
-
.where.not(status: ['trialing', 'paused'])
|
183
210
|
.includes(:customer)
|
184
211
|
.select('pay_subscriptions.*, pay_customers.processor as customer_processor')
|
185
212
|
.joins(:customer)
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
213
|
+
.where(created_at: start_date..end_date)
|
214
|
+
.where.not(status: ['trialing', 'paused'])
|
215
|
+
.sum do |subscription|
|
216
|
+
mrr = MrrCalculator.process_subscription(subscription)
|
217
|
+
days_in_period = (end_date - subscription.created_at).to_i
|
218
|
+
total_days = (subscription.current_period_end - subscription.current_period_start).to_i
|
219
|
+
prorated_days = [days_in_period, total_days].min
|
220
|
+
(mrr.to_f * prorated_days / total_days).round
|
221
|
+
end
|
190
222
|
end
|
191
223
|
|
192
224
|
def calculate_revenue_in_period(period)
|
@@ -210,7 +242,10 @@ module Profitable
|
|
210
242
|
end
|
211
243
|
|
212
244
|
def calculate_total_customers
|
213
|
-
|
245
|
+
Pay::Customer.joins(:charges)
|
246
|
+
.merge(paid_charges)
|
247
|
+
.distinct
|
248
|
+
.count
|
214
249
|
end
|
215
250
|
|
216
251
|
def calculate_total_subscribers
|
@@ -243,8 +278,9 @@ module Profitable
|
|
243
278
|
end
|
244
279
|
|
245
280
|
def calculate_average_revenue_per_customer
|
246
|
-
|
247
|
-
|
281
|
+
paying_customers = calculate_total_customers
|
282
|
+
return 0 if paying_customers.zero?
|
283
|
+
(all_time_revenue.to_f / paying_customers).round
|
248
284
|
end
|
249
285
|
|
250
286
|
def calculate_lifetime_value
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: profitable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- rameerez
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-09-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pay
|