Skip to content

Latest commit

 

History

History
600 lines (463 loc) · 24.7 KB

ProductSearch.md

File metadata and controls

600 lines (463 loc) · 24.7 KB

ProductSearch

With the release of ViSenze's Catalog system, ViSearch Android SDK will now include the Product Search API suite - an additional set of APIs that ideally can achieve the following:

  • Select the right product image for indexing for best search performance
  • Aggregate search results on a product level instead of image level
  • Consistent data type in API response with Catalog’s schema

Current stable version: 2.2.2

Minimum Android SDK Version: API 9, Android 2.3


Table of Contents

  1. Setup
  2. Initialization
  3. Solution APIs
  4. Search Parameters
  5. Search Results
  6. Search Examples
  7. Event Tracking

1. Setup

1.1 Running the Demo

The source code of a demo application is provided together with the SDK (demo). You can simply open visearch-sdk-android project in Android Studio and run the cameraDemo project.

1.2 Installing the SDK

As bintray has expired. we have moved our repository to jitpack. Add repository in your root build.gradle

allprojects {
	repositories {
		...
		maven { url 'https://jitpack.io' }
	}
}

include the dependency in your project using gradle:

implementation 'com.github.visenze:visenze-tracking-android:0.2.1'
implementation 'com.github.visenze:visearch-sdk-android:2.2.1'

1.3 Add User Permissions

Our SDK additionally needs these user permissions to work. Add the following declarations to the AndroidManifest.xml file.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.visenze.android.visenze_demo_test">

    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <application>
    ...
    </application>
</manifest>

Network permission allows the app to connect to network services. Write/read to external storage permissions allow the app to load and save images on the device.

2. Initialization

ProductSearch must be initialized with an app key and a placement id before it can be used. In order for it to be notified of the search result, ProductSearch.ResultListener callback must be provided when making the actual API call.

public class MyActivity extends Activity {
    private static final String appKey = "YOUR_APP_KEY";
    private static final Integer placementId = 1;
    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        SearchAPI.initProductSearchAPI(this, appKey, placementId);
        ...
    }
    ...
}

Please init ProductSearch client with the following if there is a need for changing the default endpoint https://search.visenze.com.

ProductSearch productSearch = new ProductSearch
                            .Builder(appKey, placementId)
                            .setApiEndPoint("https://custom-visearch.yourdomain.com")
                            .build(context);

3. Solution APIs

There are two main APIs provided in this suite, one allows searching for products based on an image input, the other searches using a product's ID. A product's ID can be retrieved from a Search Result.

3.1 Search By Image

POST /product/search_by_image

Searching by Image can happen in three different ways - by url, id or File. Assuming that you have initialized the SDK according to section 2:

  • Example of image URL:
String imageUrl = "https://some_website.com/some_image.jpg";
ProductSearchByImageParams params = new ProductSearchByImageParams(imageUrl);
ProductSeach ps = SearchAPI.getProductSearchInstance();
ps.searchByImage(params, new ProductSearch.ResultListener() {
    @Override
    public void onSearchResult(ProductResponse response, ErrorData error) {
        // LOGIC HERE
    }
});
  • Example of image ID:
public void yourFunction(ProductResponse yourPriorResponse)
{
    String imageID = yourPriorResponse.getImId();
    ProductSearchByImageParams params = new ProductSearchByImageParams(imageID);
    ProductSeach ps = SearchAPI.getProductSearchInstance();
    ps.searchByImage(params, new ProductSearch.ResultListener() {
        @Override
        public void onSearchResult(ProductResponse response, ErrorData error) {
            // LOGIC HERE
        }
    });
}

Image ID refers to the ID that is assigned to each image that the API receives. Meaning, on every successful search (via URl or File), the image will have an ID assigned to it that can be reused. The example above assumes that you have stored a prior successful ProductResponse somewhere and are using it as a parameter.

  • Example of image File:
@Override
public void OnImageCaptured(Image image, String imagePath) {
    ProductSearchByImageParams params = new ProductSearchByImageParams(image);
    ProductSeach ps = SearchAPI.getProductSearchInstance();
    ps.searchByImage(params, new ProductSearch.ResultListener() {
        @Override
        public void onSearchResult(ProductResponse response, ErrorData error) {
            // LOGIC HERE
        }
    });
}

Image File refers to an actual file with bytes representing the image (i.e. opened from file upload, or taken from camera). The example above is in a scenario where the android camera captures an image. You can construct the Image object by doing 1 of the following:

  • Using an image from a local file path:
Image image = new Image("/local/path/to/image.jpg");
  • Using an image by providing the Uri of the image in photo gallery:
Image image = new Image(context, uri);
  • Construct the image from the byte array returned by the camera preview callback:
@Override
public void onPictureTaken(byte[] bytes, Camera camera) {
    Image image = new Image(bytes);
}

3.2 Recommendations

GET /product/recommendations/{product_id}

Sample code for calling Recommendations:

...
public void yourFunction(ProductResponse yourPriorResponse)
{
    String productID = yourPriorResponse.getProducts().get(0).getProductId();
    ProductSearchByIdParams params = new ProductSearchByIdParams(productID);
    ProductSeach ps = SearchAPI.getProductSearchInstance();
    ps.recommendations(params, new ProductSearch.ResultListener()) {
        @Override
        public void onSearchResult(ProductResponse response, ErrorData error) {
            // LOGIC HERE
        }       
    }

}

The example above assumes that you have stored a prior successful ProductResponse somewhere and are using it as a parameter. The yourPriorResponse.getProducts().get(0).getProductId() is an arbitrary product ID, you should implement a proper way of choosing which product in the response's products list to use.

Pseudo steps:

  • make an initial search using an image
  • store the search results (contains the list of products)
  • use the product ID of one of these products in the next search

4. Search Parameters

Depending on which API function you call, it requires their own parameters both of which is extends off the BaseProductSearchParams class:

4.1 BaseProductSearchParams

Name Type Description
page Integer Pagination - This indicate which page should the results be retrieved from.
limit Integer Pagination - This is the number of results that per page.
vaUid String Unique string that can identify the user/app, e.g. device serial number, advertising ID or a server generated string (reqid for first search) for analytics purposes.
vaSid String Analytics session ID.
facets List<String>
facetLimit Integer
facetsShowCount Boolean
sortBy String
groupBy String
groupLimit Integer
sortGroupStrategy String
score Boolean If the score of the detection should be returned in the response.
score_min Float Minimum score of any detected object requires to be considered.
scoreMax Float Maximum score of any detected object to be considered.
returnFieldsMapping Boolean Default to false, if set to true, will return catalog fields mappings.
returnImageS3Url Boolean
attrsToGet List<String>
filters Map<String, String>
textFilters Map<String, String>
customParams Map<String, String>
vaSdk String Analytics field.
vaSdkVersion String Analytics field.
vaOs String Analytics field.
vaOsv String Analytics field.
vaDeviceBrand String Analytics field.
vaDeviceModel String Analytics field.
vaAppBundleId String Analytics field.
vaAppName String Analytics field.
vaAppVersion String Analytics field.
dedup Boolean Analytics field.
dedupScoreThreshold Double Analytics field.
vaAaid String Analytics field.
vaDidmd5 String Analytics field.
vaN1 Double Analytics field.
vaN2 Double Analytics field.
vaS1 String Analytics field.
vaS2 String Analytics field.

4.2 ProductSearchByImageParams

Name Type Description
imUrl String Image URL.
imId String Image ID (can be retrieved from a prior search response).
image Image Image file object (data).
box int[] Array of image-space coordinates of the detection box if only a specific part of the image is to be used. [x1,y1,x2,y2] format.
detection String
detectionLimit Integer
detectionSensitivity String
searchAllObjects Boolean Default to false. If set to true will return all objects (same as ViSearch /discoversearch).

4.3 ProductSearchByIdParams

Name Type Description
productId String The product's ID can be retrieved from a prior search response's Product.

5. Search Results

As the SDK performs itssearches on a separate thread, you will receive the results of the search via a callback that you will need to provide as a parameter to the search function.

  • The callback's signature:
@Override
public void onSearchResult(ProductResponse response, ErrorData error) {
    // do your code here
}

On a successful API call, the ProductResponse will be valid while ErrorData will be null, vice-versa on failure.

5.1 ProductResponse

Name Type Description
status String The request status, either OK, warning, or fail
imId String Image ID. Can be used to search again without reuploading
method String
error ErrorData Error message and code if the request was not successful i.e. when status is warning or fail
productTypes List<ProductType> Detected products' types, score and their bounding box in (x1,y1,x2,y2) format
products List<Product> The list of products in the search results. Important fields for first release. If missing, it will be set to blank. Note that we are displaying customer’s original catalog fields in “data” field
objects List<ProductObject> When the searchAllObjects parameter is set to true
catalogFieldsMapping Map<String, String> Original catalog’s fields mapping
facets List<Facet> List of facet fields value and response for filtering
page int The result page number
limit int The number of results per page
total int Total number of search result
reqId String ID assigned to the request made

5.2 ErrorData

Name Type Description
code int Error code, e.g. 401, 404 etc...
message String The server response message.

5.3 ProductType

Name Type Description
boxArray int[] The image-space coordinates of the detection box that represents the product.
type String The detected type of the product.
score double The detection's score of the product.
attributeList Map

5.4 Product

Name Type Description
productId String The product's ID which can be used in recommendations.
imageUrl String Image URL.
data Map<String, Object> This data field is slightly more complicated and deserves its own section over here.
score Float The detection score of the product.
s3Url String The product's image's S3 URL.
detect String
keyword String
boxArray int[] The image space coordinates of the detected object.

5.4.1 Data

To better explain what the data field is, take a look at the table below (database field_names):

ViSenze pre-defined catalog fields Client X's catalog original names
product_id sku
main_image_url medium_image
title product_name
product_url link
price sale_price
brand brand

The table is a representation of how ViSenze's Catalog name its fields vs how Client X's database name its fields - both fields essentially mean the same thing just named differently.

i.e. visenze_database["product_id"] == client_x_database["sku"]

You can find the schema mapping of ViSenze and the Client's in the catalogFieldsMapping variable found in ProductResponse - if the ProductSearchByImageParams have its returnFieldsMapping variable set to true when the search was called.

5.5 ProductObject

When using the searchAllObjects is set to true, the search response will return the results in a list of ProductObject instead of a list of Product directly. The difference is that ProductObject will split the products according to type.

Name Type Description
result List<Product> The list of products belonging to this type.
total Integer The total number of results in this type.

5.6 Facet

Facets are used to perform potential filtering of results.

Name Type Description
key String
items List<FacetItem>
range FacetRange

5.7 FacetItem

Facet for distinct value filtering.

Name Type Description
value String
count Integer

5.8 FacetRange

Facet for value range filtering.

Name Type Description
min Number
max Number

6. Search Examples

Here are a set of complex search examples that makes use of the other search parameters and how their response works. Lets start off by ensuring that you know what attributes/ fields are available to your app key (Attributes are set using the Dashboard when creating the App).

  • By setting returnFieldsMapping to true, you will retrieve a map of field names that can be used for other advanced searches like filtering:

    String imageUrl = "REPLACE WITH VALID URL";
    ProductSearchByImageParams params = new ProductSearchByImageParams(imageUrl);
    params.setReturnFieldsMapping(true);
    
    // The productSearch variable is a class member variable assigned via
    // SearchAPI.getProductSearchInstance() in a previous function (constructor etc)
    productSearch.searchByImage(params, new ProductSearch.ResultListener() {
        @Override
        public void onSearchResult(ProductResponse response, ErrorData error) {
            // This mapping represents how ViSenze's fields are mapped to a Client's fields
            // Visenze Field -> Client Field
            Map<String, String> mappings = response.getCatalogFieldsMapping();
    
            // The data field will contain minimal information as we did not set any attributes to get
            for (Product p: response.getProducts()) {
                Map<String, Object> data = p.getData();
            }
        }
    });
  • Once you know what attributes/fields you can use, we can try retrieving more data in the result. By default, there is not alot of information returned in the data field of the search results' products. However we can request for more information about the products by setting attrsToGet:

    String imageUrl = "REPLACE WITH VALID URL";
    ProductSearchByImageParams params = new ProductSearchByImageParams(imageUrl);
    params.setReturnFieldsMapping(true);
    
    // Tell the API to return the `merchant_category` as part of the product's `data` field.
    List<String> attributes = new ArrayList<String>();
    attributes.add("merchant_category");
    params.setAttrsToGet(attributes);
    
    // The productSearch variable is a class member variable assigned via
    // SearchAPI.getProductSearchInstance() in a previous function (constructor etc)
    productSearch.searchByImage(params, new ProductSearch.ResultListener() {
        @Override
        public void onSearchResult(ProductResponse response, ErrorData error) {
            Map<String, String> mappings = response.getCatalogFieldsMapping();
    
            // By setting attrsToGet, we can now find the `merchant_category` field in `data`.
            for (Product p: response.getProducts()) {
                Map<String, Object> data = p.getData();
            }
        }
    });

    Remember to replace "merchant_category" to a field that is valid for you (found in the catalogFieldsMapping).

  • After getting a hang of how attributes/fields work for you, we can further expand the search parameters to perform filterings as well:

    String imageUrl = "REPLACE WITH VALID URL";
    ProductSearchByImageParams params = new ProductSearchByImageParams(imageUrl);
    params.setReturnFieldsMapping(true);
    
    // Tell the API to return the `merchant_category` as part of the product's `data` field.
    List<String> attributes = new ArrayList<String>();
    attributes.add("merchant_category");
    params.setAttrsToGet(attributes);
    
    // Tell the API to filter products that have the `merchant_category` == "Clothing/Tops/Blouse"
    Map<String, String> filters = new HashMap<String,String>();
    filters.put("merchant_category", "Clothing/Tops/Blouse");
    params.setFilters(filters);
    
    // The productSearch variable is a class member variable assigned via
    // SearchAPI.getProductSearchInstance() in a previous function (constructor etc)
    productSearch.searchByImage(params, new ProductSearch.ResultListener() {
        @Override
        public void onSearchResult(ProductResponse response, ErrorData error) {
            Map<String, String> mappings = response.getCatalogFieldsMapping();
    
            // By setting `attrsToGet`, we can now find the `merchant_category` field in `data`.
            // By setting `filters`, we can now see that all products belongs to the "Clothing/Tops/Blouse" category from `data["merchant_category"]`.
            for (Product p: response.getProducts()) {
                Map<String, Object> data = p.getData();
            }
        }
    });

    Remember to replace "merchant_category" to a field that is valid for you (found in the catalogFieldsMapping).

    Remember to replace "Clothing/Tops/Blouse" to a valid value depending on your fields.

With these advance examples, you should be able to start playing around with the other parameters!

7. Event Tracking

ViSearch Android SDK provides methods to track how your customer interacts with the search results.

In addition, to improve subsequent search quality, it is recommended to send user actions when they interact with the results.

7.1 Setup Tracking

You can initialize ViSenze Analytics tracker for sending analytics events as follow.

ProductSeach productSearch = SearchAPI.getProductSearchInstance();
Tracker tracker = productSearch.newTracker(null, false);

7.2 Send Events

Currently we support the following event actions: click, view, product_click, product_view, add_to_cart, and transaction. The action parameter can be an arbitrary string and custom events can be sent.

To send events, first retrieve the search query ID found in the search results listener:

        public void onSearchResult(ProductResponse response, ErrorData error) {
            String queryId = response.getReqid();

	    // send event here
        }

Then, create the event using 1 of the helper methods Event.createXXXEvent(). For product_click, product_view events, queryId, pid, imgUrl and pos are all required.

Event.createProductClickEvent(String queryId, String pid, String imgUrl, int pos)

Event.createProductImpressionEvent(String queryId, String pid, String imgUrl, int pos)

Event.createAddCartEvent(String queryId, String pid, String imgUrl, int pos)

Event.createTransactionEvent(String queryId, String transactionId, double value)

// custom event with arbitray action
Event.createCustomEvent(String action)

Finally send the event via the tracker:

tracker.sendEvent(event);

Below are the brief description for various parameters:

Field Description Required
queryId The request id of the search request. This reqid can be obtained from all the search result:resultList.getReqid() Yes
action Event action. Currently we support the following event actions: click, view, product_click, product_view, add_to_cart, and transaction. Yes
pid Product ID ( generally this is the im_name) for this product. Can be retrieved via ImageResult.getImageName() Required for product view, product click and add to cart events
imgUrl Image URL ( generally this is the im_url) for this product. Can be retrieved via ImageResult.getImageUrl() Required for product view, product click and add to cart events
pos Position of the product in Search Results e.g. click position/ view position. Note that this start from 1 , not 0. Required for product view, product click and add to cart events
transactionId Transaction ID Required for transaction event.
value Transaction value e.g. order value Required for transaction event.
uid Unique user/device ID. If not provided, a random (non-personalizable) UUID will be generated to track the device. No
category A generic string to categorize / group the events in related user flow. For example: privacy_flow, videos, search_results. Typically, categories are used to group related UI elements. Max length: 32 No
name Event name e.g. open_app , click_on_camera_btn. Max length: 32. No
label label for main interaction object such as product title, page title. This together with action can be used to decide whether an event is unique e.g. if user clicks on same product twice, only 1 unique click . Max length: 32. No
fromReqId Generic request ID field to specify which request leads to this event e.g. click request ID that leads to the purchase. The chain can be like this queryId → clickId → purchase. Max length: 32. No
source Segment the traffic by tagging them e.g. from camera, from desktop. Max length: 32. No
brand Product brand. Max length: 64. No
price Product price. Numeric field, if provided must be >=0 and is a valid number. No
currency ISO 3 characters code e.g. “USD”. Will be validated if provided. No
productUrl Product URL. Max length: 512 No
campaign Advertising campaign. Max length : 64. No
campaignAdGroup Ad group name (only relevant for campaign) No
campaignCreative Creative name (only relevant for campaign) No
n1 Custom numeric parameter. No
n2 Custom numeric parameter. No
n3 Custom numeric parameter. No
n4 Custom numeric parameter. No
n5 Custom numeric parameter. No
s1 Custom string parameter. Max length: 64. No
s2 Custom string parameter. Max length: 64. No
s3 Custom string parameter. Max length: 64. No
s4 Custom string parameter. Max length: 64. No
s5 Custom string parameter. Max length: 64. No