Skip to content

Commit

Permalink
Only user_id and scenario is required for UserRecommendation (#108)
Browse files Browse the repository at this point in the history
  • Loading branch information
JakubTesarek authored and OndraM committed Apr 23, 2020
1 parent f9ff273 commit 691ff99
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 121 deletions.
31 changes: 16 additions & 15 deletions README.md
Expand Up @@ -182,20 +182,22 @@ when providing recommendations.
$matej = new Matej('accountId', 'apikey');

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

$recommendations = $response->getRecommendation()->getData();
```

You can also set more granular options of the recommendation command:
You can also set more granular options of the recommendation command and overwrite Matej default behavior on per-request basis:

```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()
->addBoost(Boost::create('valid_to >= NOW()', 2));
Expand Down Expand Up @@ -235,13 +237,12 @@ Every item in Matej has its id, and optionally other item properties. These prop
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`,
`item_url` and `item_title`:
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`, `item_title`:

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

$response = $matej->request()
->recommendation($recommendation)
Expand Down Expand Up @@ -320,7 +321,7 @@ $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))
->addRecommendation(UserRecommendation::create('user-id', 'emailing'))
->addRecommendations([/* array of UserRecommendation objects */])
->send();
```
Expand All @@ -332,11 +333,11 @@ but once available, you can specify which model you want to use when requesting
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')
->setModelName('alpha');

$sortingCommand = Sorting::create('user-id', ['item-id-1', 'item-id-2', 'item-id-3']);
$sortingCommand->setModelName('beta')
$sortingCommand->setModelName('beta');

$response = $matej->request()
->recommendation($recommendationCommand)
Expand All @@ -359,7 +360,7 @@ Typically, you'd select a random sample of users, to which you'd present recomme
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
34 changes: 34 additions & 0 deletions UPGRADE-3.0.md
@@ -0,0 +1,34 @@
# Upgrading from 2.x to 3.0

API client release 2.0 contains few backward incompatible changes.

This guide will help you upgrade your codebase.

## `UserRecommendation::create()` now accepts only `$user_id` and `$scenario`
`UserReccomentation::create()` accepts only two argumens: `$user_id` and `$scenario`.
Both arguments are required. All other arguments are optional their default values are
configured using admin

Recommendation command can be further parametrized using fluent API.

#### 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);
```
153 changes: 94 additions & 59 deletions src/Model/Command/UserRecommendation.php
Expand Up @@ -25,62 +25,37 @@ class UserRecommendation extends AbstractCommand implements UserAwareInterface
/** @var int */
private $rotationTime;
/** @var bool */
private $hardRotation = false;
private $hardRotation;
/** @var MinimalRelevance */
private $minimalRelevance;
/** @var string[] */
private $filters = [];
private $filters;
/** @var string */
private $filterType = self::FILTER_TYPE_MQL;
/** @var string|null */
private $modelName;
/** @var string[] */
private $responseProperties = [];
private $responseProperties;
/** @var bool */
private $allowSeen = false;
/** @var Boost[] */
private $boosts = [];

private function __construct(
string $userId,
int $count,
string $scenario,
float $rotationRate,
int $rotationTime,
array $responseProperties
) {
$this->minimalRelevance = MinimalRelevance::LOW();

private function __construct(string $userId, string $scenario)
{
$this->setUserId($userId);
$this->setCount($count);
$this->setScenario($scenario);
$this->setRotationRate($rotationRate);
$this->setRotationTime($rotationTime);
$this->setResponseProperties($responseProperties);
}

/**
* @param string $userId
* @param int $count Number of requested recommendations. The real number of recommended items could be lower or
* even zero when there are no items relevant for the user.
* @param string $scenario Name of the place where recommendations are applied - eg. 'search-results-page',
* 'emailing', 'empty-search-results, 'homepage', ...
* @param float $rotationRate How much should the item be penalized for being recommended again in the near future.
* Set from 0.0 for no rotation (same items will be recommended) up to 1.0 (same items should not be recommended).
* @param int $rotationTime Specify for how long will the item's rotationRate be taken in account and so the item
* is penalized for recommendations.
* @param string[] $responseProperties Specify which properties you want to retrieve from Matej alongside the item_id.
* @return static
*/
public static function create(
string $userId,
int $count,
string $scenario,
float $rotationRate,
int $rotationTime,
array $responseProperties = []
): self {
return new static($userId, $count, $scenario, $rotationRate, $rotationTime, $responseProperties);
public static function create(string $userId, string $scenario): self
{
return new static($userId, $scenario);
}

/**
Expand Down Expand Up @@ -117,6 +92,9 @@ public function setMinimalRelevance(MinimalRelevance $minimalRelevance): self
*/
public function addFilter(string $filter): self
{
if ($this->filters == null) {
$this->filters = [];
}
$this->filters[] = $filter;

return $this;
Expand All @@ -138,18 +116,25 @@ public function setFilters(array $filters): self

/**
* Add another response property you want returned. item_id is always returned by Matej.
*
* @param string $property
* @return $this
*/
public function addResponseProperty(string $property): self
{
Assertion::typeIdentifier($property);

if ($this->responseProperties == null) {
$this->responseProperties = [];
}
$this->responseProperties[] = $property;

return $this;
}

/**
* Set all response properties you want returned. item_id is always returned by Matej, even when you don't specify it.
* Set all response properties you want returned. item_id is always returned by Matej, even when you don't specify
* it.
*
* @param string[] $properties
* @return $this
Expand Down Expand Up @@ -180,8 +165,8 @@ public function setModelName(string $modelName): self
/**
* Allow items, that the user has already "seen"
*
* By default user won't see any items, that it has visitted (and we have recorded DetailView interaction.)
* If you want to circumvent this, and get recommendations including the ones, that the user has already visitted,
* By default user won't see any items, that it has visited (and we have recorded DetailView interaction.)
* If you want to circumvent this, and get recommendations including the ones, that the user has already visited,
* you can set the "seen" allowance here.
*/
public function setAllowSeen(bool $seen): self
Expand Down Expand Up @@ -220,39 +205,64 @@ public function getUserId(): string
return $this->userId;
}

protected function setUserId(string $userId): void
/**
* Set number of requested recommendations. The real number of recommended items could be lower or even zero when
* there are no items relevant for the user.
*
* @return $this
*/
public function setCount(int $count): self
{
Assertion::typeIdentifier($userId);
Assertion::greaterThan($count, 0);

$this->userId = $userId;
$this->count = $count;

return $this;
}

protected function setCount(int $count): void
/**
* Set how much should the item be penalized for being recommended again in the near future.
*
* @param float $rotationRate
* @return $this
*/
public function setRotationRate(float $rotationRate): self
{
Assertion::greaterThan($count, 0);
Assertion::between($rotationRate, 0, 1);

$this->count = $count;
$this->rotationRate = $rotationRate;

return $this;
}

protected function setScenario(string $scenario): void
/**
* Specify for how long will the item's rotationRate be taken in account and so the item is penalized for
* recommendations.
*
* @param int $rotationTime
* @return $this
*/
public function setRotationTime(int $rotationTime): self
{
Assertion::typeIdentifier($scenario);
Assertion::greaterOrEqualThan($rotationTime, 0);

$this->scenario = $scenario;
$this->rotationTime = $rotationTime;

return $this;
}

protected function setRotationRate(float $rotationRate): void
protected function setUserId(string $userId): void
{
Assertion::between($rotationRate, 0, 1);
Assertion::typeIdentifier($userId);

$this->rotationRate = $rotationRate;
$this->userId = $userId;
}

protected function setRotationTime(int $rotationTime): void
protected function setScenario(string $scenario): void
{
Assertion::greaterOrEqualThan($rotationTime, 0);
Assertion::typeIdentifier($scenario);

$this->rotationTime = $rotationTime;
$this->scenario = $scenario;
}

protected function assembleFiltersString(): string
Expand All @@ -279,17 +289,21 @@ protected function getCommandParameters(): array
{
$parameters = [
'user_id' => $this->userId,
'count' => $this->count,
'scenario' => $this->scenario,
'rotation_rate' => $this->rotationRate,
'rotation_time' => $this->rotationTime,
'hard_rotation' => $this->hardRotation,
'min_relevance' => $this->minimalRelevance->jsonSerialize(),
'filter' => $this->assembleFiltersString(),
'filter_type' => $this->filterType,
'properties' => $this->responseProperties,
];

if ($this->count !== null) {
$parameters['count'] = $this->count;
}

if ($this->rotationRate !== null) {
$parameters['rotation_rate'] = $this->rotationRate;
}

if ($this->rotationRate !== null) {
$parameters['rotation_time'] = $this->rotationTime;
}

if ($this->modelName !== null) {
$parameters['model_name'] = $this->modelName;
}
Expand All @@ -302,6 +316,27 @@ protected function getCommandParameters(): array
$parameters['boost_rules'] = $this->getSerializedBoosts();
}

if ($this->hardRotation !== null) {
$parameters['hard_rotation'] = $this->hardRotation;
}

if ($this->hardRotation !== null) {
$parameters['hard_rotation'] = $this->hardRotation;
}

if ($this->minimalRelevance !== null) {
$parameters['min_relevance'] = $this->minimalRelevance->jsonSerialize();
}

if ($this->filters !== null) {
$parameters['filter'] = $this->assembleFiltersString();
$parameters['filter_type'] = $this->filterType;
}

if ($this->responseProperties !== null) {
$parameters['properties'] = $this->responseProperties;
}

return $parameters;
}
}
11 changes: 4 additions & 7 deletions tests/integration/RequestBuilder/CampaignRequestBuilderTest.php
Expand Up @@ -47,13 +47,10 @@ public function shouldExecuteRecommendationAndSortingCommands(): void

private function createRecommendationCommand(string $letter): UserRecommendation
{
return UserRecommendation::create(
'user-' . $letter,
1,
'integration-test-scenario',
1,
3600
);
return UserRecommendation::create('user-' . $letter, 'integration-test-scenario')
->setCount(1)
->setRotationRate(1)
->setRotationTime(3600);
}

private function createSortingCommand(string $letter): Sorting
Expand Down

0 comments on commit 691ff99

Please sign in to comment.