diff --git a/app/Services/Stats/ExperimentFunnelCollector.php b/app/Services/Stats/ExperimentFunnelCollector.php index 6dec2525..2fbed267 100644 --- a/app/Services/Stats/ExperimentFunnelCollector.php +++ b/app/Services/Stats/ExperimentFunnelCollector.php @@ -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')]) diff --git a/tests/Feature/SendExperimentFunnelReportCommandTest.php b/tests/Feature/SendExperimentFunnelReportCommandTest.php index 0f83638b..e26d74a6 100644 --- a/tests/Feature/SendExperimentFunnelReportCommandTest.php +++ b/tests/Feature/SendExperimentFunnelReportCommandTest.php @@ -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