## What
Follow-up to the `--no-discord` flag added to `stats:experiment-funnel`.
Extends the same flag to the **other stats report commands that post to
Discord**, so any of them can be run on demand (e.g. inside the prod
container) and just printed to the console without posting:
```bash
php artisan stats:subscription-funnel --no-discord
php artisan stats:ai-cohort-report --no-discord
php artisan stats:stuck-cohort-report --no-discord
php artisan stats:daily-report --no-discord
```
## How
- The two **funnel** commands (`subscription-funnel`) already printed
their table to the console, so `--no-discord` just short-circuits the
`->send()`.
- The **cohort/daily** commands only ever built a Discord embed. A small
`App\Console\Commands\Concerns\RendersReportToConsole` trait renders an
embed (title / description / fields) as plain text, so `--no-discord`
prints a readable report to the console instead of posting.
- Default behaviour is unchanged: without the flag (and on the scheduled
weekly/daily runs) they post to Discord exactly as before.
## Tests
A `--no-discord` test added to each command (`Http::assertNothingSent()`
+ the command succeeds). Pint + Larastan + all four command suites
green.
> Note: the commands query the default DB connection, so for
**production** numbers run them inside the prod container (Coolify
terminal).
## What
Adds `stats:ai-cohort-report` — a monthly scheduled command that posts a
**weekly per-cohort** retention/conversion time series for the
onboarding AI suggestions feature to Discord.
## Design (pre/post, directional — not causal)
We measure whether shipping AI suggestions moves retention/conversion
among the users who can actually receive it. This is an **observational
pre/post** readout, deliberately chosen over a randomized A/B holdout
(low signup volume; we don't want to withhold the feature). It reports a
**correlation**, never a cause.
- **Cohort (ITT):** users who imported **≥50 transactions in their first
7 days** (a pre-treatment trait). We do *not* condition on who accepted
AI — that would reintroduce self-selection.
- **Release anchor `R`:** `MIN(ai_consents.accepted_at)`, planted by
self-accepting on deploy.
- **Metrics, all fixed-horizon from each user's own signup:**
- Retention — active ≥14d (`last_active_at ≥ signup+14d`)
- Trial — subscribed ≤14d
- Paid — `active` subscription ≤30d
- AI-acceptance — funnel-health line (descriptive, not causal)
- **Right-censoring:** too-young cohorts render as `pend`, never `0`.
- **Surge flagging:** weeks with outlier eligible volume (>2.5× median)
are flagged ⚡ so a one-time acquisition spike (e.g. the launch/YouTube
surge) isn't misread as an organic trend.
Staff/test accounts (incl. the one planting the anchor consent) are
excluded via `config('ai_suggestions.report.excluded_emails')`. Output
goes to `DISCORD_AI_COHORT_WEBHOOK_URL`, falling back to the shared
`DISCORD_WEBHOOK_URL`.
## Honest caveat (baked into every report footer)
A one-time launch+YouTube signup surge sits right before release and
there's no acquisition-source tracking, so cross-release comparisons mix
channels. Read **organic-vs-organic cohort trends over a quarter**, not
a month-one before/after diff.
## Deploy steps
1. Self-accept the AI consent in prod to plant `R`.
2. Add your email (+ staff) to `AI_SUGGESTIONS_REPORT_EXCLUDED_EMAILS`.
3. Set `DISCORD_AI_COHORT_WEBHOOK_URL` (or rely on the existing admin
webhook).
## Tests
`tests/Feature/SendAiCohortReportCommandTest.php` — 7 tests / 25
assertions covering eligibility, retention, trial/paid windows, release
anchor + pre/post split, maturity censoring, surge flagging, and Discord
delivery (incl. webhook fallback). Pint clean.