Audit and harden profitable metrics#10
Conversation
Code Review: Audit and harden profitable metrics (v0.6.0)This is a substantial correctness release — nine real bugs fixed, all with regression tests, tested across Pay 7.3–11.x and Rails 7.2–8.1. The core design choices (splitting "ever billable" from "billable right now", the Issues1.
require "rails"
require "pay"
require "active_support"
...For a Rails engine gem this is unusual. The Engine already hooks into the host app's initialization pipeline — that's the correct place to guarantee load order. A bare
Suggestion: Remove the 2. # lib/profitable/mrr_calculator.rb
def self.calculate
Profitable.send(:calculate_mrr_at, Time.current)
...
endUsing Suggestion: Either make 3. def charge_json_value_sql(key)
columns = %w[object data].select { |column| Pay::Charge.column_names.include?(column) }
...
end
def charge_payload_columns
@charge_payload_columns ||= %w[object data].select { |c| Pay::Charge.column_names.include?(c) }
endMinor, but worth doing for clarity. 4. Regression tests for Bug #11 (canceled trials) don't use The Observations / Minor Notes
SummaryTwo items warrant action before merge:
Everything else — the split billability predicates, the churn-event consolidation, the schema-aware charge query, the SQLite CAST fix, and the comprehensive regression suite — is solid work. |
|
Follow-up pushed in Acted on:
Reviewed but left unchanged:
Re-verified after the changes:
|
Code Review: Audit and Harden Profitable Metrics (0.6.0)This is a large, high-quality accuracy release. The scope is significant — 16 changed files, 9 numbered bugs fixed, ~98% test coverage — and the overall approach is sound. Below is a thorough review. OverviewThe PR hardens metric accuracy across the board: canceled trials, survivor-biased churn denominators, future-event leakage in summaries, SQLite/PostgreSQL JSON-boolean divergence, unrecognized processor statuses, and the Strengths
Issues and Suggestions1.
|
|
Follow-up pushed in Acted on:
Reviewed but left unchanged:
Re-verified after this follow-up:
|
Code Review: Audit and harden profitable metricsThis is a thorough, well-engineered release. The bug descriptions are precise, the fixes are targeted, and the regression test suite is exemplary. Below is a full pass. Architecture: Scope HierarchyThe new three-level scope hierarchy is a clean design:
This is the correct split for the monthly-summary churn denominator: using Bugs / Potential Issues1. def self.calculate
Profitable.mrr_at(Time.current).to_i
rescue => e
Rails.logger.error("Error calculating total MRR: #{e.message}")
raise Profitable::Error, "Failed to calculate MRR: #{e.message}"
end
rescue Profitable::Error
raise
rescue => e
Rails.logger.error("Error calculating total MRR: #{e.message}")
raise Profitable::Error, "Failed to calculate MRR: #{e.message}"
end2. def charge_payload_columns
@charge_payload_columns ||= %w[object data].select { |column| Pay::Charge.column_names.include?(column) }
end
3. The comment explains the choice well, but it's worth documenting one implication: a subscription created and immediately cancelled in the same second (e.g. a test/staging fixture or a Stripe webhook race) will have Nits
# Before
(mrr.to_f * 12).round
# After
mrr.to_i * 12Since
Both previously defaulted to
Better Rails idiom, avoids string quoting inconsistencies across adapters. ✓
Cleaner and lets ActiveRecord handle the join key. ✓ Test QualityThe regression suite additions are excellent:
One minor coverage gap: there's no test for SummaryThe code is correct, well-documented, and thoroughly tested. The three actionable items above are: the double-error-wrap in 97.7% line / 89.9% branch coverage across a 5-version Pay appraisal matrix is a strong signal this is ready to ship. |
Code ReviewThis is a thorough, well-structured release that fixes a meaningful collection of real metric accuracy bugs — canceled trials, survivor bias in churn denominators, SQLite boolean normalization, Pay 7-9 schema compatibility, and several status constants gaps. The test coverage is excellent (regression tests for every named bug, Four findings worth considering before merge: 1.
|
Summary
This PR is the production-ready follow-up to the committed Fable review. It keeps the broad metric audit and adds another pass over real Pay schemas, load behavior, API boundaries, and edge cases that the standalone test doubles could miss.
Metric accuracy fixes from the Fable review
Time.currentso future trial conversions do not count early.paid: falsecharges are excluded consistently across SQLite/PostgreSQL/MySQL.expiredchurns atends_at;pendingis never billable.Profitable::NumericResultfromtime_to_next_mrr_milestone, so the documented.to_readableworks.Additional production-readiness fixes
pay_charges.object, so SQL now only references columns that actually exist.pause_starts_atdo not count as current MRR but still count as historical customers/subscribers once they converted.churnhelper.delegateandactionview; declaresactionviewin the gemspec because the gem directly requires and uses Action View helpers.require "rails"added during hardening; Rails apps already load Rails before engine gems are required. The gem still directly requirespay, becausepayis a runtime dependency and should be loaded withprofitable.Profitable.mrr_at(date)as the public historical MRR snapshot API, and keeps the rawcalculate_mrr_athelper private so public methods still returnNumericResult.charge_payload_columnshelper for the schema-aware charge JSON SQL path, plus an explicit dual-payload regression test.Profitable.valuation_estimateexample and updates the 0.6.0 changelog date/context.Behavior Notes
This release intentionally changes user-visible metrics where previous values were wrong. The largest expected shifts are:
expiredsubscriptions no longer inflate active/billable metrics forever.Amount-derived subscription metrics remain fully supported out of the box for Stripe. Braintree/Paddle amount parsing still requires applications to backfill processor subscription payloads locally; the README now states this explicitly.
Automated Review Response
Addressed from the automated reviews:
require "rails"and kept the narrower direct dependency requires needed by this gem.sendcoupling with publicProfitable.mrr_at(date), then moved rawcalculate_mrr_atback behind the private boundary.charge_payload_columnsso schema-aware charge payload column detection is centralized and memoized.2026-06-20.ends_at > became_billable_atguard is intentionally strict. Changing it to>=would re-admit canceled-trial equality cases.object/datacharge regression test.Reviewed but intentionally not changed:
require "pay"inlib/profitable.rb;payis a runtime dependency andprofitableis explicitly pay-powered, so loading it directly is the safest behavior for host apps.travel_towrappers to the canceled-trial regression tests; those tests use rolling windows with explicit relative offsets and are not month-bucket-sensitive. The monthly/daily summary cases already pin time.paid_chargesSQL expression into a subquery; the reviewer marked it non-blocking, and the current SQL is clear and covered.Verification
bundle exec rake test336 runs, 516 assertions, 0 failures, 0 errors, 0 skips97.75%89.9%pay-7.3:336 runs, 516 assertions, 0 failurespay-8.3:336 runs, 516 assertions, 0 failurespay-9.0:336 runs, 516 assertions, 0 failurespay-10.0:336 runs, 516 assertions, 0 failurespay-11.0:336 runs, 516 assertions, 0 failuresrails-7.2:336 runs, 516 assertions, 0 failuresrails-8.1:336 runs, 516 assertions, 0 failures11.4.3with actual Pay migrations/models: passed (all_time_revenue: 4200,total_customers: 1,mrr: 9900,arr: 118800)7.3.0with actual data-only schema and noobjectcolumns: passed with the same expected metricsbundle exec ruby -e 'require "rails"; require "active_record"; require "action_controller"; require "profitable"; ...': passed and confirmedmrr_atis public whilecalculate_mrr_atis privatebundle exec rake build: passed (pkg/profitable-0.6.0.gem)ruby -con changed Ruby files: OKgit diff --check: OK