Skip to content

Commit

Permalink
Merge 97616d6 into 9f5663f
Browse files Browse the repository at this point in the history
  • Loading branch information
JakubTesarek committed Mar 29, 2020
2 parents 9f5663f + 97616d6 commit 1fae340
Show file tree
Hide file tree
Showing 11 changed files with 348 additions and 86 deletions.
40 changes: 29 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ 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();
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 reccomended 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 Down Expand Up @@ -318,8 +329,12 @@ $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();
```

Expand All @@ -330,8 +345,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');
->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 @@ -357,7 +375,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
32 changes: 32 additions & 0 deletions UPGRADE-3.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# 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 are arguments are required. Reccomendation 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);
```
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,
];
}
}
125 changes: 89 additions & 36 deletions src/Model/Command/UserRecommendation.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,47 +38,25 @@ class UserRecommendation extends AbstractCommand implements UserAwareInterface
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
) {
private function __construct(string $userId, string $scenario)
{
$this->minimalRelevance = MinimalRelevance::LOW();

$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 @@ -135,7 +113,11 @@ public function setFilters(array $filters): self
}

/**
* Add another response property you want returned. item_id is always returned by Matej.
* 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
{
Expand Down Expand Up @@ -189,6 +171,30 @@ public function setAllowSeen(bool $seen): self
return $this;
}

/**
* Add a boost rule to already added rules.
*
* @return $this
*/
public function addBoost(Boost $boost): self
{
$this->boosts[] = $boost;

return $this;
}

/**
* Set boosts. Removes all previously set rules.
*
* @return $this
*/
public function setBoosts(array $boosts): self
{
$this->boosts = $boosts;

return $this;
}

public function getUserId(): string
{
return $this->userId;
Expand All @@ -201,11 +207,20 @@ protected function setUserId(string $userId): void
$this->userId = $userId;
}

protected function setCount(int $count): 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::greaterThan($count, 0);

$this->count = $count;

return $this;
}

protected function setScenario(string $scenario): void
Expand All @@ -215,18 +230,36 @@ protected function setScenario(string $scenario): void
$this->scenario = $scenario;
}

protected function setRotationRate(float $rotationRate): 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::between($rotationRate, 0, 1);

$this->rotationRate = $rotationRate;

return $this;
}

protected function setRotationTime(int $rotationTime): 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::greaterOrEqualThan($rotationTime, 0);

$this->rotationTime = $rotationTime;

return $this;
}

protected function assembleFiltersString(): string
Expand All @@ -239,21 +272,37 @@ protected function getCommandType(): string
return 'user-based-recommendations';
}

protected function serializeBoosts(): array
{
return array_map(
function($boost) {return $boost->jsonSerialize(); }, $this->boosts
);
}

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 @@ -262,6 +311,10 @@ protected function getCommandParameters(): array
$parameters['allow_seen'] = $this->allowSeen;
}

if (!empty($this->boosts)) {
$parameters['boost_rules'] = $this->serializeBoosts();
}

return $parameters;
}
}
Loading

0 comments on commit 1fae340

Please sign in to comment.