option('samples')); $tolerance = max(0, (int) $this->option('tolerance-seconds')); $intervalMinutes = (int) config('lottery.draw.interval_minutes', 5); $expectedSeconds = $intervalMinutes * 60; $planner->ensureBuffer(Carbon::now('UTC')); $nowUtc = Carbon::now('UTC'); $horizon = $nowUtc->copy()->addDays(14); $businessDate = Draw::query() ->whereNotNull('draw_time') ->where('draw_time', '>', $nowUtc) ->where('draw_time', '<=', $horizon) ->where('business_date', '<', '2090-01-01') ->orderByDesc('business_date') ->value('business_date'); if ($businessDate === null) { $this->error('No upcoming draws found.'); return self::FAILURE; } $times = Draw::query() ->where('business_date', $businessDate) ->whereNotNull('draw_time') ->orderBy('sequence_no') ->limit($samples + 1) ->pluck('draw_time') ->map(fn ($t) => Carbon::parse($t)->utc()) ->values() ->all(); $this->line('business_date='.$businessDate); if (count($times) < 2) { $this->error('Not enough draws to audit.'); return self::FAILURE; } $violations = []; for ($i = 1; $i < count($times); $i++) { $delta = $times[$i]->diffInSeconds($times[$i - 1], absolute: true); if (abs($delta - $expectedSeconds) > $tolerance) { $violations[] = [ 'pair' => ($i - 1).'→'.$i, 'delta_seconds' => $delta, 'expected_seconds' => $expectedSeconds, ]; } } $this->info(sprintf( 'interval_minutes=%d expected_gap=%ds tolerance=±%ds pairs_checked=%d', $intervalMinutes, $expectedSeconds, $tolerance, count($times) - 1, )); if ($violations === []) { $this->info('PASS — all checked gaps within tolerance.'); return self::SUCCESS; } $this->error('FAIL — spacing violations:'); $this->table(['pair', 'delta_seconds', 'expected_seconds'], $violations); return self::FAILURE; } }