profitable 0.2.1 → 0.2.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +4 -4
- data/lib/profitable/version.rb +1 -1
- data/lib/profitable.rb +47 -16
- 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: e3487fdc62384e3d5900e96c7642b10d44a2d701406677702b8608585f1fd3a5
|
4
|
+
data.tar.gz: e455302bf61fc7aa7aa0f0f369406bb3fd24ebc3e9b13e56516eb89fb5508990
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b0b107c91d16eabd6867b6bb4eafc0392272a41e6380f4c1f02be690f97f994fa4cb6a17d900ab930316ec0e682b391b6d36807d041387680e81e92a8150d774
|
7
|
+
data.tar.gz: 3116c70c4ba0257c59939da5ac83a10a841df147eaba9d8a1980bb7a2e7b1103ab4cbc15317461809ad3927cf5ffd612151a7538e3cfa34bdb47364c0f96bf60
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# `profitable`
|
2
2
|
|
3
|
+
## [0.2.2] - 2024-09-01
|
4
|
+
|
5
|
+
- Improve MRR calculations with prorated churned and new MRR (hopefully fixes bad churned MRR calculations)
|
6
|
+
- Only consider paid charges for all revenue calculations (hopefully fixes bad ARPC calculations)
|
7
|
+
- Add `multiple:` parameter as another option for `estimated_valuation` (same as `at:`, just syntactic sugar)
|
8
|
+
|
3
9
|
## [0.2.1] - 2024-08-31
|
4
10
|
|
5
11
|
- 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
|
|
@@ -170,23 +170,50 @@ module Profitable
|
|
170
170
|
end
|
171
171
|
|
172
172
|
def calculate_churned_mrr(period = DEFAULT_PERIOD)
|
173
|
-
|
174
|
-
|
175
|
-
|
173
|
+
start_date = period.ago
|
174
|
+
end_date = Time.current
|
175
|
+
|
176
|
+
Pay::Subscription
|
177
|
+
.includes(:customer)
|
178
|
+
.select('pay_subscriptions.*, pay_customers.processor as customer_processor')
|
179
|
+
.joins(:customer)
|
180
|
+
.where(status: ['canceled', 'ended'])
|
181
|
+
.where('pay_subscriptions.updated_at BETWEEN ? AND ?', start_date, end_date)
|
182
|
+
.sum do |subscription|
|
183
|
+
if subscription.ends_at && subscription.ends_at > end_date
|
184
|
+
# Subscription ends in the future, don't count it as churned yet
|
185
|
+
0
|
186
|
+
else
|
187
|
+
# Calculate prorated MRR if the subscription ended within the period
|
188
|
+
end_date = [subscription.ends_at, end_date].compact.min
|
189
|
+
days_in_period = (end_date - start_date).to_i
|
190
|
+
total_days = (subscription.current_period_end - subscription.current_period_start).to_i
|
191
|
+
prorated_days = [days_in_period, total_days].min
|
192
|
+
|
193
|
+
mrr = MrrCalculator.process_subscription(subscription)
|
194
|
+
(mrr.to_f * prorated_days / total_days).round
|
195
|
+
end
|
196
|
+
end
|
176
197
|
end
|
177
198
|
|
178
199
|
def calculate_new_mrr(period = DEFAULT_PERIOD)
|
179
|
-
|
200
|
+
start_date = period.ago
|
201
|
+
end_date = Time.current
|
202
|
+
|
203
|
+
Pay::Subscription
|
180
204
|
.active
|
181
|
-
.where(pay_subscriptions: { created_at: period.ago..Time.current })
|
182
|
-
.where.not(status: ['trialing', 'paused'])
|
183
205
|
.includes(:customer)
|
184
206
|
.select('pay_subscriptions.*, pay_customers.processor as customer_processor')
|
185
207
|
.joins(:customer)
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
208
|
+
.where(created_at: start_date..end_date)
|
209
|
+
.where.not(status: ['trialing', 'paused'])
|
210
|
+
.sum do |subscription|
|
211
|
+
mrr = MrrCalculator.process_subscription(subscription)
|
212
|
+
days_in_period = (end_date - subscription.created_at).to_i
|
213
|
+
total_days = (subscription.current_period_end - subscription.current_period_start).to_i
|
214
|
+
prorated_days = [days_in_period, total_days].min
|
215
|
+
(mrr.to_f * prorated_days / total_days).round
|
216
|
+
end
|
190
217
|
end
|
191
218
|
|
192
219
|
def calculate_revenue_in_period(period)
|
@@ -210,7 +237,10 @@ module Profitable
|
|
210
237
|
end
|
211
238
|
|
212
239
|
def calculate_total_customers
|
213
|
-
|
240
|
+
Pay::Customer.joins(:charges)
|
241
|
+
.merge(paid_charges)
|
242
|
+
.distinct
|
243
|
+
.count
|
214
244
|
end
|
215
245
|
|
216
246
|
def calculate_total_subscribers
|
@@ -243,8 +273,9 @@ module Profitable
|
|
243
273
|
end
|
244
274
|
|
245
275
|
def calculate_average_revenue_per_customer
|
246
|
-
|
247
|
-
|
276
|
+
paying_customers = calculate_total_customers
|
277
|
+
return 0 if paying_customers.zero?
|
278
|
+
(all_time_revenue.to_f / paying_customers).round
|
248
279
|
end
|
249
280
|
|
250
281
|
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.2
|
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
|