__( 'Find need gaps', 'ai-eshop-optimizer' ), 'description' => __( "Return `pa_need` taxonomy terms with fewer than the threshold number of products. These are the 'thin' needs whose archive pages won't drive conversion until more SKUs carry the tag.", 'ai-eshop-optimizer' ), 'category' => \AIEO\Claude\Bootstrap::CATEGORY, 'input_schema' => self::input_schema(), 'output_schema' => array( 'type' => 'object' ), 'execute_callback' => array( __CLASS__, 'execute' ), 'permission_callback' => array( __CLASS__, 'permission_check' ), 'meta' => array( 'annotations' => array( 'readonly' => true, 'idempotent' => true, ), 'show_in_rest' => true, ), ) ); } public static function permission_check( $input = null ) { unset( $input ); return current_user_can( 'manage_woocommerce' ); } private static function input_schema() { return array( 'default' => array(), 'type' => 'object', 'properties' => array( 'threshold' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 100, 'default' => 3, 'description' => 'Maximum product count for a term to count as a gap. Defaults to 3.', ), ), ); } public static function execute( $input ) { $input = is_array( $input ) ? $input : array(); $threshold = isset( $input['threshold'] ) ? max( 1, min( 100, (int) $input['threshold'] ) ) : 3; $taxonomy = apply_filters( 'aieo_dmm_needs_taxonomy', 'pa_need' ); if ( ! taxonomy_exists( $taxonomy ) ) { return array( 'taxonomy' => $taxonomy, 'gaps' => array(), 'threshold' => $threshold ); } $terms = get_terms( array( 'taxonomy' => $taxonomy, 'hide_empty' => false, 'orderby' => 'count', 'order' => 'ASC', ) ); if ( is_wp_error( $terms ) ) { return $terms; } $gaps = array(); foreach ( (array) $terms as $t ) { $count = (int) $t->count; if ( $count < $threshold ) { $gaps[] = array( 'term_id' => (int) $t->term_id, 'name' => (string) $t->name, 'slug' => (string) $t->slug, 'product_count' => $count, 'archive_url' => get_term_link( $t ), ); } } return array( 'taxonomy' => $taxonomy, 'threshold' => $threshold, 'gaps' => $gaps, 'total' => count( $gaps ), ); } }