(int) $s['sent'], 'email_opens' => (int) ( $s['opens'] ?? 0 ), 'open_rate_pct' => (float) ( $s['open_rate'] ?? 0 ), 'clicks' => (int) $s['clicks'], 'ctr_pct' => (float) $s['ctr'], 'coupons_redeemed' => (int) $s['coupons_redeemed'], 'redeem_rate_pct' => (float) $s['redeem_rate'], 'revenue' => (float) $s['revenue'], 'revenue_per_send' => (float) $s['rev_per_send'], 'lastclick_revenue' => (float) ( $s['lastclick_revenue'] ?? 0 ), 'last_send_at' => $s['last_send_at'], ); } public static function currency() { return function_exists( 'get_woocommerce_currency' ) ? get_woocommerce_currency() : ''; } } class ListWorkflowsAbility { const ABILITY_NAME = 'aieo/list-workflows'; public static function register() { wp_register_ability( self::ABILITY_NAME, array( 'label' => __( 'List campaigns / workflows', 'ai-eshop-optimizer' ), 'description' => __( 'List the store\'s marketing campaigns (workflows): name, trigger (order completed, abandoned checkout/cart, welcome, win-back, birthday, back-in-stock, opted-in/out, guest created, scheduled, manual), status (draft/active/paused), step count, audience, and each one\'s outcome funnel (sent, clicks, coupons redeemed, attributed revenue). Read-only. Requires the Rooster One plugin.', 'ai-eshop-optimizer' ), 'category' => \AIEO\Claude\Bootstrap::CATEGORY, 'input_schema' => array( 'default' => array(), 'type' => 'object', 'properties' => array( 'status' => array( 'type' => 'string', 'enum' => array( 'any', 'active', 'paused', 'draft' ), 'description' => 'Filter by status (default any).' ) ) ), 'output_schema' => array( 'type' => 'object' ), 'execute_callback' => array( __CLASS__, 'execute' ), 'permission_callback' => array( 'AIEO\\Claude\\Abilities\\WorkflowAbilityHelpers', 'can' ), 'meta' => array( 'annotations' => array( 'readonly' => true, 'idempotent' => true ), 'show_in_rest' => true ), ) ); } public static function execute( $input = null ) { if ( ! class_exists( 'AIEO_Workflows' ) ) { return new \WP_Error( 'aieo_unavailable', __( 'The Rooster One campaigns engine is not loaded.', 'ai-eshop-optimizer' ) ); } if ( class_exists( 'AIEO_Workflow_Stats' ) ) { \AIEO_Workflow_Stats::maybe_refresh(); } $want = isset( $input['status'] ) ? sanitize_key( (string) $input['status'] ) : 'any'; $out = array(); foreach ( \AIEO_Workflows::list_all() as $w ) { if ( 'any' !== $want && $want !== ( $w['status'] ?? '' ) ) { continue; } $out[] = array( 'id' => (int) $w['id'], 'name' => (string) $w['name'], 'trigger' => (string) $w['trigger_type'], 'trigger_label' => WorkflowAbilityHelpers::trigger_label( $w['trigger_type'] ), 'status' => (string) $w['status'], 'steps' => is_array( $w['steps'] ?? null ) ? count( $w['steps'] ) : 0, 'audience_id' => (int) ( $w['audience_id'] ?? 0 ), 'funnel' => WorkflowAbilityHelpers::funnel( (int) $w['id'] ), ); } return array( 'workflows' => $out, 'count' => count( $out ), 'currency' => WorkflowAbilityHelpers::currency() ); } } class GetWorkflowAbility { const ABILITY_NAME = 'aieo/get-workflow'; public static function register() { wp_register_ability( self::ABILITY_NAME, array( 'label' => __( 'Get a campaign / workflow', 'ai-eshop-optimizer' ), 'description' => __( 'Get one campaign (workflow) in full: its trigger and configuration, order/cart rules, steps (waits, sends with channel/offer, mutations), audience, and its outcome funnel. Read-only. Requires the Rooster One plugin.', 'ai-eshop-optimizer' ), 'category' => \AIEO\Claude\Bootstrap::CATEGORY, 'input_schema' => array( 'default' => array(), 'type' => 'object', 'properties' => array( 'id' => array( 'type' => 'integer', 'description' => 'Workflow id.' ) ), 'required' => array( 'id' ) ), 'output_schema' => array( 'type' => 'object' ), 'execute_callback' => array( __CLASS__, 'execute' ), 'permission_callback' => array( 'AIEO\\Claude\\Abilities\\WorkflowAbilityHelpers', 'can' ), 'meta' => array( 'annotations' => array( 'readonly' => true, 'idempotent' => true ), 'show_in_rest' => true ), ) ); } public static function execute( $input = null ) { if ( ! class_exists( 'AIEO_Workflows' ) ) { return new \WP_Error( 'aieo_unavailable', __( 'The Rooster One campaigns engine is not loaded.', 'ai-eshop-optimizer' ) ); } $id = isset( $input['id'] ) ? (int) $input['id'] : 0; $wf = $id > 0 ? \AIEO_Workflows::get( $id ) : null; if ( ! is_array( $wf ) ) { return new \WP_Error( 'aieo_not_found', __( 'Workflow not found.', 'ai-eshop-optimizer' ) ); } $wf['trigger_label'] = WorkflowAbilityHelpers::trigger_label( $wf['trigger_type'] ?? '' ); $wf['funnel'] = WorkflowAbilityHelpers::funnel( $id ); $wf['currency'] = WorkflowAbilityHelpers::currency(); return $wf; } } class WorkflowPerformanceAbility { const ABILITY_NAME = 'aieo/workflow-performance'; public static function register() { wp_register_ability( self::ABILITY_NAME, array( 'label' => __( 'Campaign performance', 'ai-eshop-optimizer' ), 'description' => __( 'Report how the store\'s campaigns (workflows) performed: for each, the funnel sent → clicks (CTR) → coupons redeemed → attributed revenue, ranked by revenue, plus store-wide totals. Answers "how did my abandoned-cart / welcome / win-back campaign perform?". Pass an id for one campaign. Read-only. Requires the Rooster One plugin.', 'ai-eshop-optimizer' ), 'category' => \AIEO\Claude\Bootstrap::CATEGORY, 'input_schema' => array( 'default' => array(), 'type' => 'object', 'properties' => array( 'id' => array( 'type' => 'integer', 'description' => 'Optional: one workflow id.' ), 'limit' => array( 'type' => 'integer', 'description' => 'Max campaigns to return, ranked by revenue (default 25).' ), ) ), 'output_schema' => array( 'type' => 'object' ), 'execute_callback' => array( __CLASS__, 'execute' ), 'permission_callback' => array( 'AIEO\\Claude\\Abilities\\WorkflowAbilityHelpers', 'can' ), 'meta' => array( 'annotations' => array( 'readonly' => true, 'idempotent' => true ), 'show_in_rest' => true ), ) ); } public static function execute( $input = null ) { if ( ! class_exists( 'AIEO_Workflows' ) || ! class_exists( 'AIEO_Workflow_Stats' ) ) { return new \WP_Error( 'aieo_unavailable', __( 'The Rooster One campaigns engine is not loaded.', 'ai-eshop-optimizer' ) ); } \AIEO_Workflow_Stats::maybe_refresh(); $meta = array(); foreach ( \AIEO_Workflows::list_all() as $w ) { $meta[ (int) $w['id'] ] = array( 'name' => (string) $w['name'], 'status' => (string) $w['status'], 'trigger' => WorkflowAbilityHelpers::trigger_label( $w['trigger_type'] ?? '' ) ); } $id = isset( $input['id'] ) ? (int) $input['id'] : 0; $limit = isset( $input['limit'] ) ? max( 1, min( 200, (int) $input['limit'] ) ) : 25; $rows = array(); $tot = array( 'sent' => 0, 'clicks' => 0, 'coupons_redeemed' => 0, 'revenue' => 0.0 ); foreach ( \AIEO_Workflow_Stats::get_all() as $wid => $s ) { if ( $id > 0 && (int) $wid !== $id ) { continue; } $m = isset( $meta[ (int) $wid ] ) ? $meta[ (int) $wid ] : array( 'name' => 'wf_' . $wid, 'status' => '', 'trigger' => '' ); $rows[] = array( 'id' => (int) $wid, 'name' => $m['name'], 'trigger' => $m['trigger'], 'status' => $m['status'], 'sent' => (int) $s['sent'], 'clicks' => (int) $s['clicks'], 'ctr_pct' => (float) $s['ctr'], 'coupons_redeemed' => (int) $s['coupons_redeemed'], 'revenue' => (float) $s['revenue'], 'revenue_per_send' => (float) $s['rev_per_send'], 'last_send_at' => $s['last_send_at'], ); $tot['sent'] += (int) $s['sent']; $tot['clicks'] += (int) $s['clicks']; $tot['coupons_redeemed'] += (int) $s['coupons_redeemed']; $tot['revenue'] += (float) $s['revenue']; } usort( $rows, function ( $a, $b ) { return ( $b['revenue'] <=> $a['revenue'] ) ?: ( $b['sent'] <=> $a['sent'] ); } ); $rows = array_slice( $rows, 0, $limit ); return array( 'campaigns' => $rows, 'totals' => $tot, 'currency' => WorkflowAbilityHelpers::currency(), 'note' => __( 'Revenue = order value where a campaign coupon was redeemed (direct attribution). Email click tracking is coupon-link based; product-only email clicks are not counted. Push adds click + conversion signals.', 'ai-eshop-optimizer' ), ); } } class MarketingPerformanceAbility { const ABILITY_NAME = 'aieo/marketing-performance'; public static function register() { wp_register_ability( self::ABILITY_NAME, array( 'label' => __( 'All marketing performance (owned + paid)', 'ai-eshop-optimizer' ), 'description' => __( 'Report performance across ALL marketing in one place — owned automations (campaign workflows, always-on web-push triggers, broadcasts) AND paid ad campaigns — each with reach/sent, clicks, orders, revenue, spend and ROAS (paid) or revenue-per-1k-reached (owned), ranked by revenue, plus owned-vs-paid totals. Answers "what\'s working across all my marketing, per euro?". Read-only. Requires the Rooster One plugin.', 'ai-eshop-optimizer' ), 'category' => \AIEO\Claude\Bootstrap::CATEGORY, 'input_schema' => array( 'default' => array(), 'type' => 'object', 'properties' => array( 'type' => array( 'type' => 'string', 'enum' => array( 'all', 'workflow', 'push_trigger', 'broadcast', 'ad_campaign', 'paid', 'owned' ), 'description' => 'Filter by initiative type / paid / owned (default all).' ), 'limit' => array( 'type' => 'integer', 'description' => 'Max initiatives, ranked by revenue (default 40).' ), ) ), 'output_schema' => array( 'type' => 'object' ), 'execute_callback' => array( __CLASS__, 'execute' ), 'permission_callback' => array( 'AIEO\\Claude\\Abilities\\WorkflowAbilityHelpers', 'can' ), 'meta' => array( 'annotations' => array( 'readonly' => true, 'idempotent' => true ), 'show_in_rest' => true ), ) ); } public static function execute( $input = null ) { if ( ! class_exists( 'AIEO_Marketing_Stats' ) ) { return new \WP_Error( 'aieo_unavailable', __( 'The Rooster One marketing rollup is not loaded.', 'ai-eshop-optimizer' ) ); } \AIEO_Marketing_Stats::maybe_refresh(); $want = isset( $input['type'] ) ? sanitize_key( (string) $input['type'] ) : 'all'; $limit = isset( $input['limit'] ) ? max( 1, min( 300, (int) $input['limit'] ) ) : 40; $rows = array(); foreach ( \AIEO_Marketing_Stats::get_all() as $r ) { $is_paid = ( 'paid' === $r['channel'] ); if ( 'all' !== $want ) { if ( 'paid' === $want && ! $is_paid ) { continue; } if ( 'owned' === $want && $is_paid ) { continue; } if ( ! in_array( $want, array( 'all', 'paid', 'owned' ), true ) && $want !== $r['initiative_type'] ) { continue; } } $rows[] = array( 'type' => $r['initiative_type'], 'id' => $r['initiative_id'], 'name' => $r['label'], 'status' => $r['status'], 'channel' => $r['channel'], 'reach' => (int) $r['reach'], 'clicks' => (int) $r['clicks'], 'ctr_pct' => (float) $r['ctr'], 'orders' => (int) $r['orders'], 'spend' => (float) $r['spend'], 'revenue' => (float) $r['revenue'], 'roas' => $r['roas'], 'rev_per_1k' => (float) $r['rev_per_1k'], 'net' => (float) $r['net'], ); } $rows = array_slice( $rows, 0, $limit ); return array( 'initiatives' => $rows, 'totals' => \AIEO_Marketing_Stats::totals(), 'currency' => WorkflowAbilityHelpers::currency(), 'note' => __( 'Owned initiatives (workflows/push-triggers/broadcasts) have no spend → compare on revenue-per-1k-reached; paid campaigns compare on ROAS = revenue/spend. Paid revenue is last-click (order_stats); owned revenue is coupon-redemption. Ad revenue needs the analytics pipeline (order_stats) — 0 without it.', 'ai-eshop-optimizer' ), ); } } class WorkflowIncrementalityAbility { const ABILITY_NAME = 'aieo/workflow-incrementality'; public static function register() { wp_register_ability( self::ABILITY_NAME, array( 'label' => __( 'Campaign incrementality (holdout lift)', 'ai-eshop-optimizer' ), 'description' => __( 'For a campaign that reserves a HOLDOUT (a control slice deliberately not messaged), measure its TRUE incremental lift: treated-vs-held-out conversion rate and revenue-per-recipient, the percentage-point + relative lift, the incremental orders and revenue the send actually CAUSED, and a two-proportion z-test for significance. Answers "did this campaign really drive sales, or would those customers have bought anyway?". Read-only. Requires a workflow with a holdout that has run.', 'ai-eshop-optimizer' ), 'category' => \AIEO\Claude\Bootstrap::CATEGORY, 'input_schema' => array( 'default' => array(), 'type' => 'object', 'properties' => array( 'id' => array( 'type' => 'integer', 'description' => 'Workflow id.' ), 'window_days' => array( 'type' => 'integer', 'description' => 'Conversion window after dispatch (default 14, max 90).' ), ), 'required' => array( 'id' ) ), 'output_schema' => array( 'type' => 'object' ), 'execute_callback' => array( __CLASS__, 'execute' ), 'permission_callback' => array( 'AIEO\\Claude\\Abilities\\WorkflowAbilityHelpers', 'can' ), 'meta' => array( 'annotations' => array( 'readonly' => true, 'idempotent' => true ), 'show_in_rest' => true ), ) ); } public static function execute( $input = null ) { $id = isset( $input['id'] ) ? (int) $input['id'] : 0; if ( $id <= 0 ) { return new \WP_Error( 'aieo_bad_input', __( 'A workflow id is required.', 'ai-eshop-optimizer' ) ); } if ( ! class_exists( 'AIEO_Workflow_Incrementality' ) ) { return new \WP_Error( 'aieo_unavailable', __( 'Incrementality is not available.', 'ai-eshop-optimizer' ) ); } $win = isset( $input['window_days'] ) ? (int) $input['window_days'] : \AIEO_Workflow_Incrementality::DEFAULT_WINDOW; $rep = \AIEO_Workflow_Incrementality::compute( $id, $win ); $wf = class_exists( 'AIEO_Workflows' ) ? \AIEO_Workflows::get( $id ) : null; $rep['workflow'] = array( 'id' => $id, 'name' => $wf ? (string) $wf['name'] : '', 'holdout_pct' => $wf ? (int) ( $wf['trigger_config']['holdout_pct'] ?? 0 ) : 0, ); $rep['currency'] = WorkflowAbilityHelpers::currency(); return $rep; } } class WorkflowAbTestAbility { const ABILITY_NAME = 'aieo/workflow-abtest'; public static function register() { wp_register_ability( self::ABILITY_NAME, array( 'label' => __( 'Campaign A/B test results', 'ai-eshop-optimizer' ), 'description' => __( 'For a campaign whose message step runs an A/B copy test, report each variant\'s reach, open rate, click-through rate, conversions and reward, plus the current winner. The engine already leans traffic toward the winner (bandit). Read-only.', 'ai-eshop-optimizer' ), 'category' => \AIEO\Claude\Bootstrap::CATEGORY, 'input_schema' => array( 'default' => array(), 'type' => 'object', 'properties' => array( 'id' => array( 'type' => 'integer', 'description' => 'Workflow id.' ), 'step_idx' => array( 'type' => 'integer', 'description' => 'Send-step index (default 0).' ), ), 'required' => array( 'id' ) ), 'output_schema' => array( 'type' => 'object' ), 'execute_callback' => array( __CLASS__, 'execute' ), 'permission_callback' => array( 'AIEO\\Claude\\Abilities\\WorkflowAbilityHelpers', 'can' ), 'meta' => array( 'annotations' => array( 'readonly' => true, 'idempotent' => true ), 'show_in_rest' => true ), ) ); } public static function execute( $input = null ) { $id = isset( $input['id'] ) ? (int) $input['id'] : 0; if ( $id <= 0 || ! class_exists( 'AIEO_Workflow_AB' ) ) { return new \WP_Error( 'aieo_bad_input', __( 'A workflow id is required.', 'ai-eshop-optimizer' ) ); } $step = isset( $input['step_idx'] ) ? (int) $input['step_idx'] : 0; $rep = \AIEO_Workflow_AB::performance( $id, $step ); $rep['currency'] = WorkflowAbilityHelpers::currency(); $rep['note'] = __( 'Reward = clicks + 0.25·opens per recipient. The winner is the best variant once each has ≥8 recipients; traffic is already biased toward it (epsilon-greedy).', 'ai-eshop-optimizer' ); return $rep; } } class MultiTouchAttributionAbility { const ABILITY_NAME = 'aieo/multi-touch-attribution'; public static function register() { wp_register_ability( self::ABILITY_NAME, array( 'label' => __( 'Multi-touch attribution', 'ai-eshop-optimizer' ), 'description' => __( 'Credit owned marketing across the full purchase PATH, not just the last click: for each paid order, the messages (email / push) the customer engaged with beforehand share the revenue under a chosen model. Returns per-campaign credited revenue. Compare models (last_click / linear / time_decay / position) to see which campaigns last-click under-credits. Read-only.', 'ai-eshop-optimizer' ), 'category' => \AIEO\Claude\Bootstrap::CATEGORY, 'input_schema' => array( 'default' => array(), 'type' => 'object', 'properties' => array( 'model' => array( 'type' => 'string', 'enum' => array( 'last_click', 'linear', 'time_decay', 'position' ), 'description' => 'Attribution model (default time_decay).' ), 'window_days' => array( 'type' => 'integer', 'description' => 'How far before an order a touch still counts (default 30).' ), 'lookback_days' => array( 'type' => 'integer', 'description' => 'How far back to scan orders (default 90).' ), ) ), 'output_schema' => array( 'type' => 'object' ), 'execute_callback' => array( __CLASS__, 'execute' ), 'permission_callback' => array( 'AIEO\\Claude\\Abilities\\WorkflowAbilityHelpers', 'can' ), 'meta' => array( 'annotations' => array( 'readonly' => true, 'idempotent' => true ), 'show_in_rest' => true ), ) ); } public static function execute( $input = null ) { if ( ! class_exists( 'AIEO_Order_Touchpoints' ) ) { return new \WP_Error( 'aieo_unavailable', __( 'Multi-touch attribution is not available.', 'ai-eshop-optimizer' ) ); } $model = isset( $input['model'] ) ? sanitize_key( (string) $input['model'] ) : 'time_decay'; $window = isset( $input['window_days'] ) ? (int) $input['window_days'] : 30; $look = isset( $input['lookback_days'] ) ? (int) $input['lookback_days'] : 90; $rep = \AIEO_Order_Touchpoints::attribute( $window, $model, $look ); $rep['currency'] = WorkflowAbilityHelpers::currency(); return $rep; } }