mdn-backend / app /Console /Commands /RollupAnalyticsFunnels.php
internationalscholarsprogram's picture
feat(analytics): website analytics pipeline — ingestion, rollup, admin query
45dc401
Raw
History Blame Contribute Delete
3.92 kB
<?php
declare(strict_types=1);
namespace App\Console\Commands;
use App\Infrastructure\Ids\UuidGenerator;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
/**
* Aggregates analytics_funnel_progress rows started on a single calendar date
* into analytics_daily_funnels.
*
* Safe to re-run: upsert on (summary_date, module) replaces existing counts.
*
* Schedule: daily at 02:15 UTC (see bootstrap/app.php →withSchedule).
* Manual backfill: php artisan mdn:analytics:rollup-funnels --date=2026-07-10
*/
class RollupAnalyticsFunnels extends Command
{
protected $signature = 'mdn:analytics:rollup-funnels
{--date= : Date to aggregate (YYYY-MM-DD). Defaults to yesterday.}';
protected $description = 'Roll up funnel_progress rows into analytics_daily_funnels.';
public function __construct(private readonly UuidGenerator $ids)
{
parent::__construct();
}
public function handle(): int
{
$date = $this->option('date')
? Carbon::parse($this->option('date'))->toDateString()
: Carbon::yesterday()->toDateString();
$this->line("Aggregating funnel progress for {$date} …");
$rows = DB::table('analytics_funnel_progress')
->selectRaw("
DATE(started_at) AS summary_date,
module,
COUNT(*) AS starts,
SUM(IF(highest_step >= 0, 1, 0)) AS step_0_reached,
SUM(IF(highest_step >= 1, 1, 0)) AS step_1_reached,
SUM(IF(highest_step >= 2, 1, 0)) AS step_2_reached,
SUM(IF(highest_step >= 3, 1, 0)) AS step_3_reached,
SUM(IF(highest_step >= 4, 1, 0)) AS step_4_reached,
SUM(IF(highest_step >= 5, 1, 0)) AS step_5_reached,
SUM(IF(highest_step >= 6, 1, 0)) AS step_6_reached,
SUM(submitted) AS submissions,
SUM(abandoned) AS abandonments
")
->whereDate('started_at', $date)
->groupByRaw('DATE(started_at), module')
->get();
if ($rows->isEmpty()) {
$this->line(" No funnel_progress rows for {$date} — nothing to write.");
return self::SUCCESS;
}
$now = Carbon::now()->toDateTimeString();
$upsertRows = $rows->map(fn (object $r) => [
'id' => $this->ids->generate(),
'summary_date' => $r->summary_date,
'module' => $r->module,
'starts' => (int) $r->starts,
'step_0_reached' => (int) $r->step_0_reached,
'step_1_reached' => (int) $r->step_1_reached,
'step_2_reached' => (int) $r->step_2_reached,
'step_3_reached' => (int) $r->step_3_reached,
'step_4_reached' => (int) $r->step_4_reached,
'step_5_reached' => (int) $r->step_5_reached,
'step_6_reached' => (int) $r->step_6_reached,
'submissions' => (int) $r->submissions,
'abandonments' => (int) $r->abandonments,
'created_at' => $now,
'updated_at' => $now,
])->all();
DB::table('analytics_daily_funnels')->upsert(
$upsertRows,
['summary_date', 'module'],
[
'starts', 'step_0_reached', 'step_1_reached', 'step_2_reached',
'step_3_reached', 'step_4_reached', 'step_5_reached', 'step_6_reached',
'submissions', 'abandonments', 'updated_at',
],
);
$this->info(" Done. {$rows->count()} module(s) written for {$date}.");
return self::SUCCESS;
}
}