fix(stats): count soft-deleted users in the experiment funnel

The base user query dropped soft-deleted accounts (SoftDeletes scope), so
their variant assignment, connection cost and burn vanished from the report
- biased asymmetrically (prod: 8/9/10 deleted across control/reduced/pay_now)
and precisely on the cohort that most embodies connect-and-leave, since
deleting the account is the strongest form of it. Add withTrashed().
This commit is contained in:
Víctor Falcón 2026-07-15 07:40:18 +02:00
parent ab3fbe38a9
commit b8e1f9c2d8
2 changed files with 19 additions and 0 deletions

View File

@ -94,6 +94,10 @@ class ExperimentFunnelCollector
$monthlyEquiv = $this->monthlyEquivByPriceId();
User::query()
// Soft-deleted accounts still count: they were assigned a variant and
// their bank connections incurred real cost, and deleting the account
// is itself an experiment outcome (the strongest "connect and leave").
->withTrashed()
->where('users.created_at', '>=', $startedAt)
->when($excluded !== [], fn ($query) => $query->whereNotIn('email', $excluded))
->with(['subscriptions' => fn ($query) => $query->where('type', 'default')])

View File

@ -173,6 +173,21 @@ it('computes connection cost, wasted burn and contribution margin', function ()
->and($control['activationToPaidRate'])->toBe(0.5); // 1 paid ÷ 2 activated
});
it('still counts soft-deleted users so their assignment and connection cost survive', function () {
$signup = CarbonImmutable::parse('2026-06-05');
$user = experimentUser(SubscriptionExperiment::CONTROL, $signup, null, connections: 2);
$user->delete(); // soft delete the account
$control = app(ExperimentFunnelCollector::class)->collect()['variants'][SubscriptionExperiment::CONTROL];
expect($control['assigned'])->toBe(1)
->and($control['assignedMature'])->toBe(1)
->and($control['activated'])->toBe(1) // 2 connections
->and($control['costCents'])->toBe(80) // 2 × 40, still charged
->and($control['wastedCostCents'])->toBe(80); // never paid → pure burn
});
it('reports a matured-cohort conversion capped at 100% and prints the matured denominator', function () {
$signup = CarbonImmutable::parse('2026-06-05'); // control matures by the test clock