whisper-money/app/Console/Commands/SendAiCohortReportCommand.php

118 lines
4.1 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Services\Ai\AiCohortReportCollector;
use App\Services\Discord\DiscordWebhook;
use Carbon\CarbonImmutable;
use Illuminate\Console\Command;
class SendAiCohortReportCommand extends Command
{
protected $signature = 'stats:ai-cohort-report {--weeks= : Number of weekly cohorts to include}';
protected $description = 'Post the weekly AI-suggestions cohort retention/conversion report to Discord';
public function __construct(private AiCohortReportCollector $collector)
{
parent::__construct();
}
public function handle(): int
{
$weeks = $this->option('weeks') !== null ? (int) $this->option('weeks') : null;
$report = $this->collector->collect($weeks);
$webhookUrl = config('services.discord.ai_cohort_webhook_url')
?: config('services.discord.webhook_url');
(new DiscordWebhook($webhookUrl))->send('', [$this->buildEmbed($report)]);
$this->info('AI cohort report sent to Discord.');
return self::SUCCESS;
}
/**
* @param array{releaseAt: ?CarbonImmutable, releaseWeek: ?string, weeks: list<array<string, mixed>>} $report
* @return array<string, mixed>
*/
private function buildEmbed(array $report): array
{
$lines = [sprintf('%-9s %5s %6s %6s %6s %5s', 'Week', 'Elig', 'Ret', 'Trial', 'Paid', 'AI')];
foreach ($report['weeks'] as $row) {
$flags = '';
if ($report['releaseWeek'] !== null && $row['week'] === $report['releaseWeek']) {
$flags .= ' 🚀';
}
if ($row['surge']) {
$flags .= ' ⚡';
}
$lines[] = sprintf(
'%-9s %5d %6s %6s %6s %5s%s',
$row['week'],
$row['eligible'],
$this->rateCell($row['retainedRate'], $row['retentionMature'], $row['eligible']),
$this->rateCell($row['trialRate'], $row['retentionMature'], $row['eligible']),
$this->rateCell($row['paidRate'], $row['paidMature'], $row['eligible']),
$this->aiCell($row['aiAcceptedRate'], $row['eligible']),
$flags,
);
}
$release = $report['releaseAt'] !== null
? 'First AI consent (release anchor): '.$report['releaseAt']->format('D, d M Y').' · week '.$report['releaseWeek'].' 🚀'
: 'No AI consent recorded yet — feature not live in production.';
return [
'title' => '🤖 AI Suggestions — Weekly Cohort Report',
'description' => "```\n".implode("\n", $lines)."\n```",
'color' => 0x5865F2,
'fields' => [
[
'name' => 'Release anchor',
'value' => $release,
'inline' => false,
],
[
'name' => 'Legend',
'value' => 'Elig = users with ≥50 transactions in their first 7 days · Ret = active ≥14d after signup · Trial = subscribed ≤14d · Paid = active subscription ≤30d · AI = accepted AI consent · `pend` = cohort too young to score · ⚡ = signup surge',
'inline' => false,
],
[
'name' => '⚠️ Directional only',
'value' => 'Pre/post comparison, not a randomised test. Cohorts are compared at equal age. Surge weeks (⚡, e.g. launch/YouTube) differ in acquisition channel and are not controlled — compare organic weeks like-for-like. Confidence builds over a quarter, not a single month.',
'inline' => false,
],
],
];
}
private function rateCell(?float $rate, bool $mature, int $eligible): string
{
if ($eligible === 0) {
return '—';
}
if (! $mature || $rate === null) {
return 'pend';
}
return ((int) round($rate * 100)).'%';
}
private function aiCell(?float $rate, int $eligible): string
{
if ($eligible === 0 || $rate === null) {
return '—';
}
return ((int) round($rate * 100)).'%';
}
}