Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
"plannr/laravel-fast-refresh-database": "^1.2"
},
"suggest": {
"laravel/boost": "Required for AI-assisted development guidelines and skills",
"laravel/mcp": "Required for MCP server tools integration",
"spatie/laravel-activitylog": "Required for included activity log based stats"
},
"autoload": {
Expand Down
41 changes: 41 additions & 0 deletions resources/boost/guidelines/core.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
## Javaabu Stats

This package provides time-series statistics for Laravel applications, supporting aggregate count and sum queries with filters, formatters, date ranges, and CSV export.

### Architecture

Stats are managed through a central static registry `TimeSeriesStats` and follow a repository pattern:

- `CountStatsRepository` — counts rows grouped by time period. Implement `query(): Builder`, `getTable(): string`, `getAggregateFieldName(): string`.
- `SumStatsRepository` — sums a numeric field grouped by time period. Same methods as count, plus `getFieldToSum(): string`.

Stat classes live in `App\Stats\TimeSeries\` and are registered via `TimeSeriesStats::register()` in a service provider's `boot()` method with snake_case metric names.

### Artisan Generator

Generate and auto-register a stat class: `php artisan stats:time-series {Name} {Model} --type={count|sum}`.

### Filters

Define allowed filters by returning `Filter` objects from `allowedFilters()` using the `StatsFilter` factory:

- `StatsFilter::exact('name', 'column')` — matches a column value exactly
- `StatsFilter::scope('name', 'scopeMethod')` — calls an Eloquent query scope
- `StatsFilter::closure('name', fn)` — applies arbitrary query logic

### Formatters

Five built-in formatters: `default`, `chartjs`, `sparkline`, `flot`, `combined`. Register custom formatters via `TimeSeriesStats::registerFormatters()`.

### Routes

- `TimeSeriesStats::registerApiRoute()` — registers a GET endpoint returning JSON stats data
- `TimeSeriesStats::registerRoutes()` — registers GET (web view) and POST (CSV export) endpoints

### Authorization

Override `canView(?Authorizable $user)` on a stat repository to control access. The default checks for `view_stats` permission. The `stats.view-time-series` middleware alias is auto-registered.

### Configuration

Publish with `php artisan vendor:publish --tag=stats-config`. Other publish tags: `stats-views`, `stats-stubs`. Key options in `config/stats.php`: `week_starts_on_sunday`, `date_locale`, `date_formats`, `default_time_series_mode`, `default_date_range`, `framework`, `default_layout`.
159 changes: 159 additions & 0 deletions resources/boost/skills/stats-development/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
---
name: stats-development
description: Use when creating stat classes, registering metrics, adding filters, setting up stats routes, or refactoring existing stats code with javaabu/stats.
---

# Stats Development

## When to use this skill

Use when creating stat repositories, registering metrics, adding filters, or setting up stats routes with `javaabu/stats`.

## Core Principle: Use What Exists

The package provides built-in controllers, routes, middleware, formatters, and export. Your job is to generate stat repository classes for each model — not to reimplement infrastructure. Specifically:

- **Use the Artisan generator** to scaffold stat classes — it auto-registers them too
- **Use `registerApiRoute()` / `registerRoutes()`** — never write custom route handlers for stats
- **Use built-in formatters** (`default`, `chartjs`, `sparkline`, `flot`, `combined`) before creating custom ones
- **Use the `stats.view-time-series` middleware** — it's auto-registered, don't recreate auth logic
- **Use `ExportsTimeSeriesStats` trait** in existing controllers for CSV export — don't build export from scratch

When refactoring existing stats code, check for: direct filter class instantiation (should use `StatsFilter` factory), manual route definitions (should use `registerApiRoute`/`registerRoutes`), and reimplemented formatting or export logic.

## Creating a Stat

Place stat classes in `app/Stats/TimeSeries/`. Extend `CountStatsRepository` for row counts or `SumStatsRepository` for numeric sums. One class per model/metric — each stat targets a single table.

**Quick path — Artisan generator (auto-registers in AppServiceProvider):**

```bash
php artisan stats:time-series OrdersCount Order --type=count
php artisan stats:time-series PaymentAmounts Payment --type=sum
```

**Manual — Count stat with filters** (namespace `App\Stats\TimeSeries`, import `StatsFilter`, `CountStatsRepository`, `Builder`):

```php
class OrdersCount extends CountStatsRepository
{
public function query(): Builder { return Order::query(); }
public function getTable(): string { return 'orders'; }
public function getAggregateFieldName(): string { return 'count'; }

public function allowedFilters(): array
{
return [
StatsFilter::exact('customer', 'customer_id'),
StatsFilter::exact('status', 'status'),
];
}
}
```

**For a sum stat**, extend `SumStatsRepository` instead and add one extra method:

```php
public function getFieldToSum(): string
{
return 'amount';
}
```

## Registering Stats

Register in `AppServiceProvider::boot()`. Metric names must be snake_case.

```php
use Javaabu\Stats\TimeSeriesStats;

public function boot(): void
{
TimeSeriesStats::register([
'orders_count' => OrdersCount::class,
'payment_amounts' => PaymentAmounts::class,
]);
}
```

To suppress built-in user_signups/user_logins stats: `TimeSeriesStats::excludeDefaultStats();`

## Filters

Always use the `StatsFilter` factory. Never instantiate filter classes directly.

```php
use Javaabu\Stats\Filters\StatsFilter;

public function allowedFilters(): array
{
return [
// Exact column match
StatsFilter::exact('customer', 'customer_id'),

// Eloquent query scope — calls $query->whereActive()
StatsFilter::scope('active', 'whereActive'),

// Custom closure — receives ($query, $value, $stat)
StatsFilter::closure('min_amount', function (Builder $query, $value, $stat) {
return $query->where('amount', '>=', $value);
}),
];
}
```

Pass filters when creating a stat instance:

```php
$stats = TimeSeriesStats::createFromMetric('orders_count', PresetDateRanges::THIS_YEAR, [
'customer' => 5,
'status' => 'completed',
]);
```

## Routes

**API route (JSON) — register in `routes/api.php`:**

```php
use Javaabu\Stats\TimeSeriesStats;

// IMPORTANT: Do NOT include /api in the URL — routes/api.php adds it automatically.
TimeSeriesStats::registerApiRoute('/stats/time-series', 'stats.time-series.index');
```

**Admin routes (web view + CSV export):**

```php
TimeSeriesStats::registerRoutes('/stats/time-series', 'stats.index', 'stats.export', ['auth', 'stats.view-time-series']);
```

The `stats.view-time-series` middleware alias is auto-registered by the package.

## Quick Reference

| What | How |
|------|-----|
| Base classes | `CountStatsRepository`, `SumStatsRepository` |
| Register metrics | `TimeSeriesStats::register(['name' => Class::class])` |
| Create instance | `TimeSeriesStats::createFromMetric('name', $dateRange, $filters)` |
| Time modes | `TimeSeriesModes::HOUR\|DAY\|WEEK\|MONTH\|YEAR` |
| Date ranges | `PresetDateRanges::THIS_YEAR\|LAST_30_DAYS\|LAST_7_DAYS\|...` |
| Custom range | `new ExactDateRange('2024-01-01', '2024-12-31')` |
| Format output | `$stat->format('chartjs', TimeSeriesModes::DAY)` |
| Built-in formats | `default`, `chartjs`, `sparkline`, `flot`, `combined` |
| Get total | `$stat->total()` |
| Custom date col | Override `getDateFieldName()` (default: `created_at`) |
| Authorization | Override `canView(?Authorizable $user)` (default: `view_stats` permission) |

See `references/` for formatters, export, authorization, and advanced features.

## Verify Against Live State

If the app has `laravel/mcp` installed, use the MCP tools to cross-check before writing code:

- **ListMetrics** — confirm which metrics are registered, their filters, and aggregate fields
- **ListFormatters** — confirm available formatters (including custom ones)
- **QueryStat** — test a metric with real data before building on top of it

This avoids guessing metric names or filter keys — the MCP tools reflect the actual running application.
71 changes: 71 additions & 0 deletions resources/boost/skills/stats-development/references/advanced.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Advanced Features

## Authorization

Override `canView()` to customize per-stat access control. Default requires `view_stats` permission.

```php
use Illuminate\Contracts\Auth\Access\Authorizable;

public function canView(?Authorizable $user = null): bool
{
return $user && $user->can('view_order_stats');
}
```

Check if any stat is viewable: `TimeSeriesStats::canViewAny($user)`

## Custom Date Field

Override `getDateFieldName()` to group by a column other than `created_at`:

```php
public function getDateFieldName(): string
{
return 'completed_at';
}
```

## Login & Signup Repositories

Built-in abstract repos for tracking user auth activity. Extend and implement `userModelClass()`:

```php
use Javaabu\Stats\Repositories\TimeSeries\SignupsRepository;

class CustomerSignups extends SignupsRepository
{
public function userModelClass(): string
{
return \App\Models\Customer::class;
}
}
```

For logins (requires `spatie/laravel-activitylog`), extend `LoginsRepository` the same way.

The package auto-registers `user_signups` and `user_logins` for the default User model. Suppress with `TimeSeriesStats::excludeDefaultStats()`.

## Configuration

Publish: `php artisan vendor:publish --tag=stats-config`

Key options in `config/stats.php`:

| Option | Default | Description |
|--------|---------|-------------|
| `week_starts_on_sunday` | `true` | Week grouping start day |
| `date_locale` | `en_GB` | Date format locale |
| `default_time_series_mode` | `DAY` | Default grouping granularity |
| `default_date_range` | `LAST_7_DAYS` | Default date range |
| `framework` | `material-admin-26` | CSS framework for views |

Other publish tags: `stats-views`, `stats-stubs`.

## MCP Tools

Three read-only MCP tools for AI agent integration (requires `laravel/mcp`):

- **ListMetrics** — lists all registered metrics with classes, aggregate fields, and allowed filters
- **QueryStat** — queries a metric with parameters (mode, date range, filters, format)
- **ListFormatters** — lists available output formats
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Querying Stats & Formatting Output

## Querying Stats Programmatically

```php
use Javaabu\Stats\TimeSeriesStats;
use Javaabu\Stats\Enums\PresetDateRanges;
use Javaabu\Stats\Enums\TimeSeriesModes;

// Create a stat instance
$stat = TimeSeriesStats::createFromMetric('orders_count', PresetDateRanges::LAST_30_DAYS, [
'status' => 'completed',
]);

// Get results grouped by time mode
$results = $stat->results(TimeSeriesModes::DAY); // Collection of [count, day]

// Get formatted output for a charting library
$chartData = $stat->format('chartjs', TimeSeriesModes::DAY);

// Get aggregate total
$total = $stat->total();
```

## Preset Date Ranges

`PresetDateRanges` enum values: `TODAY`, `YESTERDAY`, `THIS_WEEK`, `LAST_WEEK`, `THIS_MONTH`, `LAST_MONTH`, `THIS_YEAR`, `LAST_YEAR`, `LAST_7_DAYS`, `LAST_14_DAYS`, `LAST_30_DAYS`, `LAST_5_YEARS`, `LAST_10_YEARS`, `LIFETIME`.

For custom ranges:

```php
use Javaabu\Stats\Support\ExactDateRange;

$range = new ExactDateRange('2024-01-01', '2024-12-31');
$stat = TimeSeriesStats::createFromMetric('orders_count', $range);
```

## Comparison Periods

```php
$current = PresetDateRanges::THIS_MONTH;
$previous = $current->getPreviousDateRange();

$currentStats = TimeSeriesStats::createFromMetric('orders_count', $current);
$previousStats = TimeSeriesStats::createFromMetric('orders_count', $previous);
```

## Time Series Modes

`TimeSeriesModes` enum: `HOUR`, `DAY`, `WEEK`, `MONTH`, `YEAR`. Controls SQL grouping granularity.

```php
$stat->results(TimeSeriesModes::MONTH); // Monthly aggregation
$stat->format('chartjs', TimeSeriesModes::WEEK); // Weekly chart data
```

## Built-in Formatters

`default`, `chartjs`, `sparkline`, `flot`, `combined`. Request via API with `?format=chartjs`.

## Creating a Custom Formatter

Extend `AbstractTimeSeriesStatsFormatter` and implement `format()`:

```php
<?php

namespace App\Formatters;

use Javaabu\Stats\Contracts\TimeSeriesStatsRepository;
use Javaabu\Stats\Enums\TimeSeriesModes;
use Javaabu\Stats\Formatters\TimeSeries\AbstractTimeSeriesStatsFormatter;

class ReactFormatter extends AbstractTimeSeriesStatsFormatter
{
public function format(TimeSeriesModes $mode, TimeSeriesStatsRepository $stats, ?TimeSeriesStatsRepository $compare = null): array
{
$field = $stats->getAggregateFieldName();
$results = $stats->results($mode);

return [
'labels' => $results->pluck($mode->value)->toArray(),
'values' => $results->pluck($field)->toArray(),
'total' => $stats->total(),
];
}
}
```

Register in a service provider:

```php
TimeSeriesStats::registerFormatters([
'react' => \App\Formatters\ReactFormatter::class,
]);
```
Loading
Loading