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 9, 2020
1 parent f9ff273 commit 87e1d4a
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 86 deletions.
32 changes: 20 additions & 12 deletions README.md
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,9 +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()
->addBoost(Boost::create('valid_to >= NOW()', 2));
Expand Down Expand Up @@ -235,12 +237,11 @@ 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`,
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 @@ -320,8 +321,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 @@ -332,8 +337,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 @@ -359,7 +367,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
@@ -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);
```
88 changes: 52 additions & 36 deletions src/Model/Command/UserRecommendation.php
Expand Up @@ -41,46 +41,22 @@ class UserRecommendation extends AbstractCommand implements UserAwareInterface
/** @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 @@ -137,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 @@ -227,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 @@ -241,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 @@ -279,17 +286,26 @@ 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 Down
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
Expand Up @@ -79,13 +79,10 @@ public function shouldReturnInvalidCommandOnInvalidPropertyName(): void

private function createRecommendationCommand(string $username): UserRecommendation
{
return UserRecommendation::create(
$username,
5,
'integration-test-scenario',
0.50,
3600
);
return UserRecommendation::create($username, 'integration-test-scenario')
->setCount(5)
->setRotationRate(0.50)
->setRotationTime(3600);
}

private function assertShorthandResponse(
Expand Down
25 changes: 13 additions & 12 deletions tests/unit/Model/Command/UserRecommendationTest.php
Expand Up @@ -10,18 +10,15 @@ class UserRecommendationTest extends TestCase
/** @test */
public function shouldBeInstantiableViaNamedConstructorWithDefaultValues(): void
{
$command = UserRecommendation::create('user-id', 333, 'test-scenario', 1.0, 3600);
$command = UserRecommendation::create('user-id', 'test-scenario');

$this->assertInstanceOf(UserRecommendation::class, $command);
$this->assertSame(
$this->assertEquals(
[
'type' => 'user-based-recommendations',
'parameters' => [
'user_id' => 'user-id',
'count' => 333,
'scenario' => 'test-scenario',
'rotation_rate' => 1.0,
'rotation_time' => 3600,
'hard_rotation' => false,
'min_relevance' => MinimalRelevance::LOW,
'filter' => '',
Expand All @@ -46,7 +43,10 @@ public function shouldUseCustomParameters(): void
$rotationTime = random_int(1, 86400);
$modelName = 'test-model-' . md5(microtime());

$command = UserRecommendation::create($userId, $count, $scenario, $rotationRate, $rotationTime);
$command = UserRecommendation::create($userId, $scenario)
->setCount($count)
->setRotationRate($rotationRate)
->setRotationTime($rotationTime);

$command->setMinimalRelevance(MinimalRelevance::HIGH())
->enableHardRotation()
Expand All @@ -57,7 +57,8 @@ public function shouldUseCustomParameters(): void
->addBoost(Boost::create('custom = argument', 2.0));

$this->assertInstanceOf(UserRecommendation::class, $command);
$this->assertSame(

$this->assertEquals(
[
'type' => 'user-based-recommendations',
'parameters' => [
Expand Down Expand Up @@ -86,7 +87,7 @@ public function shouldUseCustomParameters(): void
/** @test */
public function shouldAssembleMqlFilters(): void
{
$command = UserRecommendation::create('user-id', 333, 'test-scenario', 1.0, 3600);
$command = UserRecommendation::create('user-id', 'test-scenario');

// Default filter
$this->assertSame('', $command->jsonSerialize()['parameters']['filter']);
Expand Down Expand Up @@ -118,12 +119,12 @@ public function shouldAssembleMqlFilters(): void
/** @test */
public function shouldAllowModificationOfResponseProperties(): void
{
$command = UserRecommendation::create('user-id', 333, 'test-scenario', 1.0, 3600, ['test']);
$command = UserRecommendation::create('user-id', 'test-scenario');
$command->addResponseProperty('test');
$this->assertSame(['test'], $command->jsonSerialize()['parameters']['properties']);

// Add some properties
$command->addResponseProperty('url');

$this->assertSame(['test', 'url'], $command->jsonSerialize()['parameters']['properties']);

// Overwrite all properties
Expand All @@ -134,7 +135,7 @@ public function shouldAllowModificationOfResponseProperties(): void
/** @test */
public function shouldResetBoostRules(): void
{
$command = UserRecommendation::create('user-id', 333, 'test-scenario', 1.0, 3600)
$command = UserRecommendation::create('user-id', 'test-scenario')
->addBoost(Boost::create('valid_to >= NOW()', 1.0));

$command->setBoosts([
Expand All @@ -154,7 +155,7 @@ public function shouldResetBoostRules(): void
/** @test */
public function shouldNotIncludeEmptyBoosts(): void
{
$command = UserRecommendation::create('user-id', 333, 'test-scenario', 1.0, 3600)
$command = UserRecommendation::create('user-id', 'test-scenario')
->setBoosts([]);

$this->assertArrayNotHasKey('boost_rules', $command->jsonSerialize()['parameters']);
Expand Down

0 comments on commit 87e1d4a

Please sign in to comment.