Skip to content

Commit

Permalink
Merge 3b006ac into 9f5663f
Browse files Browse the repository at this point in the history
  • Loading branch information
JakubTesarek committed Mar 29, 2020
2 parents 9f5663f + 3b006ac commit 1ac91cd
Show file tree
Hide file tree
Showing 20 changed files with 765 additions and 242 deletions.
60 changes: 39 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

# PHP API Client for Matej recommendation engine

[![Latest Stable Version](https://img.shields.io/packagist/v/lmc/matej-client.svg?style=flat-square)](https://packagist.org/packages/lmc/matej-client)
Expand All @@ -8,7 +9,7 @@

This library requires PHP 7.1+. However, we provide also PHP 5.6-compatible version [`matej-client-php5`](https://github.com/lmc-eu/matej-client-php5).

Please note the PHP 5.6 version is just transpiled copy of this library - examples, pull requests, issues, changelog etc. are placed in this repository.
Please note that the PHP 5.6 version is just transpiled copy of this library - examples, pull requests, issues, changelog etc. are placed in this repository.

## Installation

Expand Down Expand Up @@ -41,8 +42,7 @@ $ composer require lmc/matej-client php-http/curl-client guzzlehttp/psr7 # use l

## Usage

To start using Matej you will need your account id (database name) and secret API key - both of them must be obtained
from LMC R&D team.
To start using Matej you will need your account id (database name) and secret API key - both of them must be obtained from LMC R&D team.

First create an instance of `Matej` object:
```php
Expand All @@ -61,7 +61,7 @@ Once finished with building the request, use `send()` method to execute it and r
```php
$response = $matej->request()
->events()
->addInteraction(\Lmc\Matej\Model\Command\Interaction::purchase('user-id', 'item-id'))
->addInteraction(\Lmc\Matej\Model\Command\Interaction::withItem('purchases', 'user-id', 'item-id'))
->addUserMerge(...)
...
->send();
Expand Down Expand Up @@ -155,7 +155,7 @@ $matej = new Matej('accountId', 'apikey');
$response = $matej->request()
->events()
// Add interaction between user and item
->addInteraction(Interaction::purchase('user-id', 'item-id'))
->addInteraction(Interaction::withItem('purchases', 'user-id', 'item-id'))
->addInteractions([/* array of Interaction objects */])
// Update item data
->addItemProperty(ItemProperty::create('item-id', ['valid_from' => time(), 'title' => 'Title']))
Expand All @@ -182,8 +182,8 @@ when providing recommendations.
$matej = new Matej('accountId', 'apikey');

$response = $matej->request()
->recommendation(UserRecommendation::create('user-id', 5, 'test-scenario', 1.0, 3600))
->setInteraction(Interaction::purchase('user-id', 'item-id')) // optional
->recommendation(UserRecommendation::create('user-id', 'test-scenario'))
->setInteraction(Interaction::withItem('purchases', 'user-id', 'item-id')) // optional
->setUserMerge(UserMerge::mergeInto('user-id', 'source-id')) // optional
->send();

Expand All @@ -193,8 +193,11 @@ $recommendations = $response->getRecommendation()->getData();
You can also set more granular options of the recommendation command:

```php
$recommendation = UserRecommendation::create('user-id', 5, 'test-scenario', 1.0, 3600);
$recommendation->setFilters(['for_recommendation = 1'])
$recommendation = UserRecommendation::create('user-id', 'test-scenario')
->setCount(5)
->setRotationRate(1.0)
->setRotationTime(3600)
->setFilters(['for_recommendation = 1'])
->setMinimalRelevance(MinimalRelevance::HIGH())
->enableHardRotation();

Expand Down Expand Up @@ -227,18 +230,26 @@ $recommendations = $response->getRecommendation()->getData();
// }
```

You can further modify which items will be recommended by providing boosting rules. Priority of items matching the
MQL `$criteria` will be multiplied by the value of `multiplier`:

```php
$reccomendation = UserRecommendation::create('user-id', 'test-scenario')
->addBoost(Boost::create('valid_to >= NOW()', 2))
->addBoost(Boost::create('for_recommendation = 1', 3.5));
```

#### Recommendation response properties

Every item in Matej has its id, and optionally other item properties. These properties can be set up in [item properties setup](#item-properties-setup-to-setup-you-matej-database),
and you can upload item data in the [events](#send-events-data-to-matej) request. This has major benefit because you can request
these properties to be returned as part of your Recommendation Request.

We call them response properties, and they can be specified either as the last parameter of `UserRecommendation::create` function,
by calling `->addResponseProperty()` method, or by calling `->setResponseProperties()` method. Following will request an `item_id`,
We call them response properties. They can be specified by calling `->addResponseProperty()` method or by calling `->setResponseProperties()` method. Following will request an `item_id`,
`item_url` and `item_title`:

```php
$recommendation = UserRecommendation::create('user-id', 5, 'test-scenario', 1.0, 3600, ['item_url'])
$recommendation = UserRecommendation::create('user-id', 'test-scenario')
->addResponseProperty('item_title');

$response = $matej->request()
Expand All @@ -264,7 +275,7 @@ $recommendedItems = $response->getRecommendation()->getData();
```

If you don't specify any response properties, Matej will return an array of `stdClass` instances, which contain only `item_id` property.
If you do request at least one response property, you don't need to metion `item_id`, as Matej will always return it regardless of the
If you do request at least one response property, you don't need to mention `item_id`, as Matej will always return it regardless of the
properties requested.

If you request an unknown property, Matej will return a `BAD REQUEST` with HTTP status code `400`.
Expand All @@ -283,7 +294,7 @@ $matej = new Matej('accountId', 'apikey');

$response = $matej->request()
->sorting(Sorting::create('user-id', ['item-id-1', 'item-id-2', 'item-id-3']))
->setInteraction(Interaction::purchase('user-id', 'item-id')) // optional
->setInteraction(Interaction::withItem('purchases', 'user-id', 'item-id')) // optional
->setUserMerge(UserMerge::mergeInto('user-id', 'source-id')) // optional
->send();

Expand Down Expand Up @@ -318,20 +329,27 @@ $response = $matej->request()
->addSorting(Sorting::create('user-id', ['item-id-1', 'item-id-2', 'item-id-3']))
->addSortings([/* array of Sorting objects */])
// Request user-based recommendations
->addRecommendation(UserRecommendation::create('user-id', 10, 'emailing', 1.0, 3600))
->addRecommendations([/* array of UserRecommendation objects */])
->addRecommendation(
UserRecommendation::create('user-id', 'emailing')
->setCount(10)
->setRotationRate(1.0)
->setRotationTime(3600)
)->addRecommendations([/* array of UserRecommendation objects */])
->send();
```

### A/B Testing support
`Recommendation` and `Sorting` commands support optional A/B testing of various models. This has to be set up in Matej first,
but once available, you can specify which model you want to use when requesting recommendations or sortings.
but once available, you can specify which model you want to use when requesting recommendations or sorting.

This is available for `recommendation`, `sorting` and `campaign` requests:

```php
$recommendationCommand = UserRecommendation::create('user-id', 5, 'test-scenario', 1.0, 3600);
$recommendationCommand->setModelName('alpha');
$recommendationCommand = UserRecommendation::create('user-id', 'test-scenario');
->setCount(5)
->setRotationRate(1.0)
->setRotationTime(3600)
->setModelName('alpha');

$sortingCommand = Sorting::create('user-id', ['item-id-1', 'item-id-2', 'item-id-3']);
$sortingCommand->setModelName('beta')
Expand All @@ -353,11 +371,11 @@ $response = $matej->request()

If you don't provide any model name, the request will be sent without it, and Matej will use default model for your instance.

Typically, you'd select a random sample of users, to which you'd present recommendations and sortings from second model. This way, implementation
Typically, you'd select a random sample of users, to which you'd present recommendations and sorting from second model. This way, implementation
in your code should look similar to this:

```php
$recommendation = UserRecommendation::create('user-id', 5, 'test-scenario', 1.0, 3600);
$recommendation = UserRecommendation::create('user-id', 'test-scenario')

if ($session->isUserInBucketB()) {
$recommendation->setModelName('alpha');
Expand Down
110 changes: 110 additions & 0 deletions UPGRADE-3.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@

# Upgrading from 2.x to 3.0

API client release 3.0 contains few backward incompatible changes.

This guide will help you upgrade your codebase.

## `UserRecommendation::create()` now accepts only `$userId` and `$scenario`
`UserReccomentation::create()` accepts only two arguments: `$user_id` and `$scenario`.

Both arguments are required.

All other arguments can be now set using new setters and are optional:
- `setCount(int $count)`
- `setRotationRate(float $rotationRate)`
- `setRotationTime(int $rotationTime)`

#### Before
```php
$recommendation = UserRecommendation::create('user-id', 5, 'scenario', 1.0, 3600);
```

#### After
```php
$recommendation = UserRecommendation::create('user-id', 'scenario')
->setCount(5)
->setRotationRate(1.0)
->setRotationTime(3600);
```

which is equivalent to

```php
$recommendation = UserRecommendation::create('user-id', 'scenario');
$recommendation->setCount(5);
$recommendation->setRotationRate(1.0);
$recommendation->setRotationTime(3600);
```

## `Interaction` now accepts interaction type as parameter
Matej now allows configuration of custom interaction types and interaction attributes.

At the same time, it allows specifying interaction item using item alias instead
of item ID. For that reason we removed static constructor methods for creating specific interaction types - `Interaction::detailView`, `Interaction::purchase`, `Interaction::bookmark` and `Interaction::rating`.

We replaced them with constructors for creating Interaction based on `$itemId` or `$itemIdAlias`:

```php
Interaction::withItem(
string $interactionType,
string $userId,
string $itemId,
int $timestamp = null
```

```php
Interaction::withAliasedItem(
string $interactionType,
string $userId,
array $itemIdAlias,
int $timestamp = null):
```

The first argument is always a string representing interaction type. Consult the table bellow to find out the correct value to fill in:

| Before: constructor method | After: argument $interactionType |
|------------------------------|----------------------------------|
| `Interaction::detailView` | `"detailviews"` |
| `Interaction::purchase` | `"purchases"` |
| `Interaction::bookmark` | `"bookmarks"` |
| `Interaction::rating` | `"ratings"` |

> To request new interaction types, please contact Matej support.
#### Before
```php
$interaction = Interaction::bookmark('user-id', 'item_id', time());
```

#### After
```php
$interaction = Interaction::withItem('bookmarks', 'user-id', 'item_id', time());
```
> Argument `$timestamp` remains optional.
## `Interaction` command supports custom attributes
Interactions now support custom attributes. These can be added using fluent API
methods `setAttribute()` and `setAttributes()`.

Argument `value` was removed from constructor methods and has to be set using new attribute methods. Its real name might have change as well. For example, for interaction type `bookmarks`, it was renamed to `stars`.

**Attribute `context` is no longer supported and was removed.**

#### Before
```php
$interaction = Interaction::rating('user-id', 'item_id', 0.5);
```

#### After
```php
$interaction = Interaction::create('ratings', 'user-id', 'item_id')
->setAttribute('stars', 0.5)
```

which is equivalent to

```php
$interaction = Interaction::create('ratings', 'user-id', 'item_id')
$interaction->setAttribute('stars', 0.5)
```
2 changes: 0 additions & 2 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ parameters:
path: tests/
- message: '#Parameter .+ of class Lmc\\Matej\\Matej constructor expects string#'
path: tests/integration/IntegrationTestCase.php
- message: '#Parameter .+ of function forward_static_call_array#'
path: tests/unit/Model/Command/InteractionTest.php
- message: '#Parameter \#4 \$body of method Http\\Message\\RequestFactory::createRequest\(\) expects#'
path: src/Http/RequestManager.php
- '#Unsafe usage of new static\(\)#'
Expand Down
53 changes: 53 additions & 0 deletions src/Model/Command/Boost.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php declare(strict_types=1);

namespace Lmc\Matej\Model\Command;

use Lmc\Matej\Model\Assertion;

/**
* Boosting items is a way how to modify results returend by Matej by specifying
* rules to increase items relevance.
*/
class Boost
{
/** @var string */
private $query;
/** @var float */
private $multiplier;

private function __construct(string $query, float $multiplier)
{
$this->setQuery($query);
$this->setMultiplier($multiplier);
}

/**
* Create boost rule to prioritize items
*
* @return static
*/
public static function create(string $query, float $multiplier): self
{
return new static($query, $multiplier);
}

public function setQuery(string $query): void
{
$this->query = $query;
}

public function setMultiplier(float $multiplier): void
{
Assertion::greaterThan($multiplier, 0);

$this->multiplier = $multiplier;
}

public function jsonSerialize(): array
{
return [
'query' => $this->query,
'multiplier' => $this->multiplier,
];
}
}
19 changes: 0 additions & 19 deletions src/Model/Command/Constants/InteractionType.php

This file was deleted.

Loading

0 comments on commit 1ac91cd

Please sign in to comment.