Analytics - v1.3.0

AI Features

Since 1.3.0

ArtisanPack UI Analytics ships four AI-powered features that plug into artisanpack-ui/ai to turn raw analytics into narrative insights. Every feature is opt-in, feature-flagged, and available as a Livewire component, React component, Vue component, and JSON API endpoint.

The four features are:

Feature key Purpose Output shape
analytics.insight_summary Streaming plain-language summary of what changed over a date range { summary, highlights[], concerns[] }
analytics.explain_anomaly Ranked hypotheses for a traffic spike or drop { hypotheses:[{cause,confidence,evidence[]}], recommended_next_steps[] }
analytics.segment_insight Non-obvious patterns in a referrer, page, or time segment { patterns:[{observation,significance,suggested_action}] }
analytics.digest_email Narrative body of the opt-in weekly/monthly digest email { summary, key_points[], caveats[] }

Each feature ships an Agent class (InsightSummaryAgent, AnomalyExplanationAgent, SegmentInsightAgent, DigestEmailAgent), a Livewire trigger + blade, React and Vue components, and is registered via AnalyticsServiceProvider::aiFeatures(). All triggers render a disabled state when the feature is toggled off, so it is safe to ship them behind a config flag.

Enabling AI Features

Every feature is opt-in per-app via config/artisanpack.php, which merges the AI package's feature registry. Toggle features on with the enabled flag:

'ai' => [
    'features' => [
        'analytics.insight_summary' => [ 'enabled' => true ],
        'analytics.explain_anomaly' => [ 'enabled' => true ],
        'analytics.segment_insight' => [ 'enabled' => true ],
        'analytics.digest_email'    => [ 'enabled' => true ],
    ],
],

Features default to disabled. Anything published to the frontend (a Livewire component or React trigger) checks the registry on render and gracefully collapses to a disabled state if the feature is off.

Authorization

All AI API endpoints are gated behind the analytics.ai.use ability. The shipped default allows any authenticated user through so upgrades are non-breaking:

Gate::define( 'analytics.ai.use', fn ( $user ) => $user !== null );

Override the gate in your AuthServiceProvider to enforce a stricter policy — for example, restricting AI usage to admins or to seats that have purchased AI quota:

Gate::define( 'analytics.ai.use', function ( User $user ) {
    return $user->hasRole( 'admin' ) || $user->team->has_ai_quota;
} );

Insight Summary

Streams a plain-language summary of a date range's metrics, calling out highlights and concerns.

Livewire

<livewire:artisanpack-analytics::ai.insight-summary
    :date-from="'2026-06-01'"
    :date-to="'2026-06-07'"
    :metrics="$weeklyMetrics"
/>

React

import { InsightSummary } from '@/vendor/artisanpack-analytics/react/components/ai';

<InsightSummary
    api={ { baseUrl: '/api/analytics' } }
    dateRange={ { from: '2026-06-01', to: '2026-06-07' } }
    metrics={ weeklyMetrics }
    compareTo={ { from: '2026-05-25', to: '2026-05-31' } }
/>

Vue

<InsightSummary
    :api="{ baseUrl: '/api/analytics' }"
    :date-range="{ from: '2026-06-01', to: '2026-06-07' }"
    :metrics="weeklyMetrics"
/>

API

POST /api/analytics/ai/insight-summary

Request body:

{
    "date_range": { "from": "2026-06-01", "to": "2026-06-07" },
    "metrics": { "visitors": 12483, "pageviews": 41230, "bounce_rate": 0.42 },
    "compare_to": { "from": "2026-05-25", "to": "2026-05-31" }
}

Response: streams a text/event-stream with the final structured payload as the last event.

Anomaly Explanation

Given a detected anomaly and lightweight context, returns ranked hypotheses for the cause.

Livewire

<livewire:artisanpack-analytics::ai.anomaly-explanation
    :anomaly="$anomaly"
    :context="$context"
/>

$anomaly shape:

[
    'metric'    => 'visitors',
    'direction' => 'up',
    'magnitude' => 2.4,      // multiplier vs baseline
    'date'      => '2026-06-05',
]

$context shape:

[
    'recent_content_changes' => [ /* ... */ ],
    'referrer_deltas'        => [ /* ... */ ],
    'campaign_launches'      => [ /* ... */ ],
]

API

POST /api/analytics/ai/explain-anomaly

Returns:

{
    "hypotheses": [
        { "cause": "Referrer surge from Hacker News", "confidence": 0.82, "evidence": [ "..." ] }
    ],
    "recommended_next_steps": [ "..." ]
}

Segment Insight

Surfaces patterns in a segment (referrer, page, or time window) relative to a baseline.

Livewire

<livewire:artisanpack-analytics::ai.segment-insight
    :segment="[ 'type' => 'referrer', 'value' => 'news.ycombinator.com' ]"
    :metrics="$segmentMetrics"
    :baseline="$overallMetrics"
/>

API

POST /api/analytics/ai/segment-insight

Response:

{
    "patterns": [
        {
            "observation": "This segment converts 3.1× the site baseline.",
            "significance": "high",
            "suggested_action": "Explore paid amplification of the referring content."
        }
    ]
}

Digest Email

Composes the AI-narrated body of an opt-in weekly or monthly digest email. Users pick their cadence via the DigestSubscription component, and a queued SendDigestEmailJob composes and sends each digest.

Subscription component

<livewire:artisanpack-analytics::ai.digest-subscription />

The component persists the current user's preference (off | weekly | monthly) to the analytics_digest_preferences table. React and Vue equivalents (DigestSubscription) ship in the frontend bundle for Inertia apps.

Scheduling digests

Dispatch digests on your chosen cadence with the shipped artisan command:

// bootstrap/app.php (or routes/console.php)
Schedule::command( 'analytics:digests:dispatch --cadence=weekly' )->weeklyOn( 1, '08:00' );
Schedule::command( 'analytics:digests:dispatch --cadence=monthly' )->monthlyOn( 1, '08:00' );

The command queues one SendDigestEmailJob per subscribed user. Users with a preference of off (or no preference row) are skipped, so it is safe to run the command against a superset.

Custom mailable

DigestEmailMailable renders resources/views/emails/digest.blade.php, a text-first template with structured headings for accessibility. Publish the view to customize it:

php artisan vendor:publish --tag=analytics-views

Feature Registration

The AnalyticsServiceProvider::aiFeatures() method is what the AI package's feature registry reads at boot time. If you fork or extend the package and want to add a fifth feature — a custom agent, for example — return an additional entry from that method:

public function aiFeatures(): array
{
    return array_merge( parent::aiFeatures(), [
        'analytics.retention_summary' => [
            'agent'       => RetentionSummaryAgent::class,
            'package'     => 'my-app/analytics-extensions',
            'label'       => __( 'Retention summary' ),
            'description' => __( 'Weekly retention cohort narrative.' ),
        ],
    ] );
}

Cost and Rate Limiting

Every request routes through the AI package's shared client, so any rate limits, budget guards, or provider fallbacks configured there apply automatically. See the artisanpack-ui/ai documentation for provider configuration.

Streaming responses (currently analytics.insight_summary) send incremental tokens over text/event-stream; the final event is the structured JSON payload. Non-streaming features return a plain JSON response.