refactor(stats): fix PHPDoc placement and naming in the price funnel

Move the collect() @param/@return block off the class docblock onto the method
it describes (matching ExperimentFunnelCollector), rename the shadowing $high2
Wilson bound to $upper, and add array-shape docblocks to the CM moment helpers.
No behaviour change.
This commit is contained in:
Víctor Falcón 2026-07-18 19:16:07 +02:00
parent f7cee87a2a
commit 327e9b1d8b
2 changed files with 28 additions and 20 deletions

View File

@ -154,8 +154,8 @@ class SendPriceExperimentFunnelReportCommand extends Command
continue;
}
[$low, $high2] = $this->significance->wilsonInterval($k, $n);
$lines[] = sprintf(' %-8s %6s [%6s %6s] (n=%d)', $label, $this->percent($k / $n), $this->percent($low), $this->percent($high2), $n);
[$lower, $upper] = $this->significance->wilsonInterval($k, $n);
$lines[] = sprintf(' %-8s %6s [%6s %6s] (n=%d)', $label, $this->percent($k / $n), $this->percent($lower), $this->percent($upper), $n);
$arms[$key] = new BinomialProportion($label, $k, $n);
}
@ -171,12 +171,19 @@ class SendPriceExperimentFunnelReportCommand extends Command
return $lines;
}
/**
* @param array<string, mixed> $row
*/
private function cmMean(array $row): float
{
return $row['assignedMature'] > 0 ? (float) $row['contributionMarginCents'] / $row['assignedMature'] : 0.0;
}
/** Sample variance (n1) of per-user CM, from the accumulated moments. */
/**
* Sample variance (n1) of per-user CM, from the accumulated moments.
*
* @param array<string, mixed> $row
*/
private function cmVariance(array $row): float
{
$n = (int) $row['assignedMature'];

View File

@ -31,23 +31,6 @@ use Laravel\Cashier\Subscription;
* Enable Banking actually bills us; API-key providers are free). It is symmetric
* across arms, so it biases absolute CM but not the between-arm comparison. Make it
* per-provider only if absolute margin ever needs to be trusted.
*
* @param int $costPerConnectionCents flat estimated monthly cost per active connection
* @return array{
* startedAt: ?CarbonImmutable,
* currency: string,
* revenueAvailable: bool,
* costPerConnectionCents: int,
* decisionWindowDays: int,
* variants: array<string, array{
* assigned: int, activated: int, subscribed: int,
* assignedMature: int, activatedMature: int, convertedMature: int, activeMature: int,
* payerCount: int, payerConnectionSum: int,
* conversionRate: ?float,
* mrrCents: int, costCents: int, contributionMarginCents: int,
* cmPerAssignedCents: ?int, cmSumSq: float, connectionsPerPayer: ?float,
* }>
* }
*/
class PriceExperimentFunnelCollector
{
@ -56,6 +39,24 @@ class PriceExperimentFunnelCollector
public function __construct(private MonthlyEquivalentPrices $prices) {}
/**
* @param int $costPerConnectionCents flat estimated monthly cost per active connection
* @return array{
* startedAt: ?CarbonImmutable,
* currency: string,
* revenueAvailable: bool,
* costPerConnectionCents: int,
* decisionWindowDays: int,
* variants: array<string, array{
* assigned: int, activated: int, subscribed: int,
* assignedMature: int, activatedMature: int, convertedMature: int, activeMature: int,
* payerCount: int, payerConnectionSum: int,
* conversionRate: ?float,
* mrrCents: int, costCents: int, contributionMarginCents: int,
* cmPerAssignedCents: ?int, cmSumSq: float, connectionsPerPayer: ?float,
* }>
* }
*/
public function collect(int $costPerConnectionCents = 40): array
{
$startedValue = config('subscriptions.price_experiment.started_at');