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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf77ba92fb941bb21624c1f677bbfff37d874fefa06050e1fe6b395ae55ea3e2
4
- data.tar.gz: 8d4cf7614e186d7082692faa36794916111205293a45aac055565c2e7923a24b
3
+ metadata.gz: e3487fdc62384e3d5900e96c7642b10d44a2d701406677702b8608585f1fd3a5
4
+ data.tar.gz: e455302bf61fc7aa7aa0f0f369406bb3fd24ebc3e9b13e56516eb89fb5508990
5
5
  SHA512:
6
- metadata.gz: 73c2251e3d2566b1d22f7319d179f9d8f1077dd70354add0fb8718232f094f613fd4aef048a0accbba8214d5ac4911497d4c6704199cf17470be6ed5ad485e06
7
- data.tar.gz: 30abc5f18398a6e1553effc66b59407f43610a90a01448dae90dab8eeb74198921593c3e6cb8bea9b63cce14f9435eccb69a53c1d71912b9214392b3e7375e20
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(at: "5x").to_readable # => "$500,000"
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 number, and/or ignore the "at:" keyword altogether
107
- Profitable.estimated_valuation(4.5).to_readable # => "$450,000"
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"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Profitable
4
- VERSION = "0.2.1"
4
+ VERSION = "0.2.2"
5
5
  end
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 = 3, at: nil)
50
- actual_multiplier = at || multiplier
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
- churned_subscriptions(period).sum do |subscription|
174
- MrrCalculator.process_subscription(subscription)
175
- end
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
- new_subscriptions = Pay::Subscription
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
- new_subscriptions.sum do |subscription|
188
- MrrCalculator.process_subscription(subscription)
189
- end
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
- actual_customers.count
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
- return 0 if total_customers.zero?
247
- (all_time_revenue.to_f / total_customers).round
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.1
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-08-31 00:00:00.000000000 Z
11
+ date: 2024-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pay