Skip to content

Commit

Permalink
Merge d146d97 into 1ca0b3a
Browse files Browse the repository at this point in the history
  • Loading branch information
JakubTesarek committed Dec 17, 2020
2 parents 1ca0b3a + d146d97 commit c3de3a7
Show file tree
Hide file tree
Showing 35 changed files with 1,371 additions and 549 deletions.
35 changes: 32 additions & 3 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
- cron: '0 3 * * *'

jobs:
tests:
unit-tests:
runs-on: ubuntu-latest
strategy:
matrix:
Expand All @@ -19,7 +19,7 @@ jobs:
- { php-version: '7.1', dependencies: '--prefer-lowest' }
- { php-version: '8.0', dependencies: '--ignore-platform-req=php' }

name: PHP ${{ matrix.php-version }} ${{ matrix.dependencies }}
name: PHP ${{ matrix.php-version }} ${{ matrix.dependencies }} (unit tests)

steps:
- uses: actions/checkout@v2
Expand All @@ -43,14 +43,43 @@ jobs:
- name: Install dependencies
run: composer update --no-interaction --no-progress --no-suggest ${{ matrix.dependencies }}

- name: Run tests
run: |
mkdir -p build/logs/
vendor/bin/phpunit --testsuite unit --colors=always --coverage-clover build/logs/clover.xml
- name: Submit coverage to Coveralls
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
composer global require --dev php-coveralls/php-coveralls
~/.composer/vendor/bin/php-coveralls --coverage_clover=./build/logs/clover.xml -v
integration-tests:
name: "Integration tests"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '7.4'
extensions: mbstring, intl, zip
coverage: xdebug
tools: composer:v2

- name: Install dependencies
run: composer update --no-interaction --no-progress --no-suggest

- name: Run tests
env:
MATEJ_TEST_ACCOUNTID: ${{ secrets.MATEJ_TEST_ACCOUNTID }}
MATEJ_TEST_APIKEY: ${{ secrets.MATEJ_TEST_APIKEY }}
MATEJ_TEST_BASE_URL: ${{ secrets.MATEJ_TEST_BASE_URL }}
run: |
mkdir -p build/logs/
vendor/bin/phpunit --colors=always --coverage-clover build/logs/clover.xml
vendor/bin/phpunit --testsuite functional --colors=always --coverage-clover build/logs/clover.xml
- name: Submit coverage to Coveralls
env:
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
<!-- There is always Unreleased section on the top. Subsections (Added, Changed, Fixed, Removed) should be added as needed. -->

## Unreleased
### Added
- Add support for new recommendation types - `UserUserRecommendation`, `ItemUserRecommendation`, `ItemItemRecommendation`.

### Changed
- **BC BREAK** | Remove deprecated constant `FILTER_TYPE_MQL` from `UserRecommendation`.
- **BC BREAK** | Rename class `UserRecommendation` to `UserItemRecommendation`.
- **BC BREAK** | Rename class `MinimalRelavance` to `ItemMinimalRelevance`.
- **BC BREAK** | Rename class `Sorting` to `ItemSorting`.

## 3.1.0 - 2020-10-13
### Fixed
Expand Down
59 changes: 43 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,33 +186,60 @@ If that happens, you should resend the entire request later, as no commands were

This has been implemented so that we don't lose any pushed data. Simple sleep of 100ms should be enough.

### Recommendations for single user
### Requesting recommendations

You can get recommendations for a single user using `recommendation()` builder.
You can request 4 types of recommendations from Matej. Each of them is represented by a specific recommendation command class:

- Items to user - UserItemRecommendation
- Items to item - ItemItemRecommendation
- Users to user - UserUserRecommendation
- Users to item - ItemUserRecommendation

For example, you can get recommendations for a single user using `recommendation()` builder.
You can attach most recent interaction and user merge event to the request so that they're taken into account
when providing recommendations.

```php
$matej = new Matej('accountId', 'apikey');

$response = $matej->request()
->recommendation(UserRecommendation::create('user-id', 'test-scenario'))
->recommendation(UserItemRecommendation::create('user-id', 'test-scenario'))
->setInteraction(Interaction::withItem('purchases', '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 and overwrite Matej default behavior on per-request basis:
You can also set more granular options of the recommendation command and overwrite Matej default behavior on per-request basis.

Each type of recommendation command supports different customization options. See table below.


#### Available recommendation attributes

| Attribute | Methods | UserItemRecommendation | UserUserRecommendation | ItemItemRecommendation | ItemUserRecommendation |
|---------------|-------------------------------------------|------------------------|------------------------|------------------------|------------------------|
| scenario | in constructor |||||
| count | `setCount` |||||
| rotation_rate | `setRotationRate` |||||
| rotation_time | `setRotationTime` |||||
| hard_rotation | `enableHardRotation` |||||
| allow_seen | `setAllowSeen` |||||
| min_relevance | `setMinimalRelevance` | `ItemMinimalRelevance` ||| `UserMinimalRelevance` |
| filter | `addFilter` `setFilters` |||||
| boost_rules | `addBoost` `setBoosts` |||||
| model_name | `setModelName` |||||
| properties | `addResponseProperty` `setResponseProperties` |||||


```php
$recommendation = UserRecommendation::create('user-id', 'test-scenario')
$recommendation = UserItemRecommendation::create('user-id', 'test-scenario')
->setCount(5)
->setRotationRate(1.0)
->setRotationTime(3600)
->setFilters(['for_recommendation = 1'])
->setMinimalRelevance(MinimalRelevance::HIGH())
->setMinimalRelevance(ItemMinimalRelevance::HIGH())
->enableHardRotation()
// You can further modify which items will be recommended by providing boosting rules.
// Priority of items matching the query will be multiplied by the value of multiplier:
Expand Down Expand Up @@ -256,7 +283,7 @@ these properties to be returned as part of your Recommendation Request.
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', 'test-scenario')
$recommendation = UserItemRecommendation::create('user-id', 'test-scenario')
->addResponseProperty('item_title')
->addResponseProperty('item_url');

Expand Down Expand Up @@ -301,7 +328,7 @@ and user merge event in one request, to make them taken into account when execut
$matej = new Matej('accountId', 'apikey');

$response = $matej->request()
->sorting(Sorting::create('user-id', ['item-id-1', 'item-id-2', 'item-id-3']))
->sorting(ItemSorting::create('user-id', ['item-id-1', 'item-id-2', 'item-id-3']))
->setInteraction(Interaction::withItem('purchases', 'user-id', 'item-id')) // optional
->setUserMerge(UserMerge::mergeInto('user-id', 'source-id')) // optional
->send();
Expand Down Expand Up @@ -334,25 +361,25 @@ $matej = new Matej('accountId', 'apikey');
$response = $matej->request()
->campaign()
// Request item sortings
->addSorting(Sorting::create('user-id', ['item-id-1', 'item-id-2', 'item-id-3']))
->addSorting(ItemSorting::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', 'emailing'))
->addRecommendation(UserItemRecommendation::create('user-id', 'emailing'))
->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,
`Recommendation` and `ItemSorting` 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 sorting.

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

```php
$recommendationCommand = UserRecommendation::create('user-id', 'test-scenario')
$recommendationCommand = UserItemRecommendation::create('user-id', 'test-scenario')
->setModelName('alpha');

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

$response = $matej->request()
Expand All @@ -376,7 +403,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', 'test-scenario');
$recommendation = UserItemRecommendation::create('user-id', 'test-scenario');

if ($session->isUserInBucketB()) {
$recommendation->setModelName('alpha');
Expand All @@ -396,7 +423,7 @@ There are two ways how to remove user data, but both of them aren't reversible a
the user ever again:

* Preferred way is to `anonymize` the user, which will randomly generate unique identifiers for all personal data,
and change that identifier across all databases and logfiles. This way the users behaviour will stay in Matej database,
and change that identifier across all databases and logfiles. This way the users behavior will stay in Matej database,
and therefore **will continue to contribute to the recommendation model**, but you won't be able to identify the user.
Thus his profile will be effectively frozen (as no new interactions can come in.) **New user id is generated server-side**,
so there is no going back after issuing the request.
Expand Down
79 changes: 79 additions & 0 deletions UPGRADE-4.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Upgrading from 3.x to 4.0

API client release 4.0 contains backward incompatible changes.

This guide will help you upgrade your codebase.

## Sorting renamed to ItemSorting
Class `Sorting` was renamed to `ItemSorting`. All its methods and behavior remained the same.

#### Before
```php
$response = $matej->request()
->sorting(Sorting::create('user-id', ['item-id-1', 'item-id-2', 'item-id-3']))
->send();
```

#### After
```php
$response = $matej->request()
->sorting(ItemSorting::create('user-id', ['item-id-1', 'item-id-2', 'item-id-3']))
->send();
```


## New types of recommendations
API client now supports new types of recommendation commands:
- Recommend items to user (`UserItemRecommendation`, originally `UserRecommendation`)
- Recommend users to user (`UserUserRecommendation`)
- Recommend items to item (`ItemItemRecommendation`)
- Recommend users to item (`ItemUserRecommendation`)

Each recommendation type supports different parameter options.
Available parameters are documented in [readme](README.md#available-recommendation-attributes).

## Recommending items to users
Class `UserRecommendation` was renamed to `UserItemRecommendation`. Class `MinimalRelevance` was
renamed to `ItemMinimalRelevance`. All other methods and attributes remained unchanged.

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

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

## New recommendation types
New types of recommendations can be created in a similar way to `UserItemRecommendation`. For example:

### Users that might be interested in an item
```php
$recommendation = ItemUserRecommendation::create('item-id', 'scenario')
->setCount(5)
->setAllowSeen(false);
```

### Users that are similar to a user
```php
$recommendation = UserUserRecommendation::create('user-id', 'scenario')
->setCount(5)
->setRotationRate(1.0)
->setRotationTime(3600);
```

### Items that are similar to an item
```php
$recommendation = ItemItemRecommendation::create('item-id', 'scenario')
->setCount(5);
```
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
"all": [
"@lint",
"@analyze",
"./vendor/bin/phpunit --colors=always"
"@test"
],
"analyze": [
"vendor/bin/ecs check src/ tests/ --ansi",
Expand All @@ -73,6 +73,9 @@
"vendor/bin/parallel-lint -j 10 ./src ./tests",
"@composer validate",
"@composer normalize --dry-run"
],
"test": [
"./vendor/bin/phpunit --colors=always"
]
}
}
36 changes: 36 additions & 0 deletions src/Model/Command/AbstractItemRecommendation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php declare(strict_types=1);

namespace Lmc\Matej\Model\Command;

use Lmc\Matej\Model\Assertion;

/**
* Deliver recommendations for given item.
*/
abstract class AbstractItemRecommendation extends AbstractRecommendation
{
/** @var string */
private $itemId;

protected function __construct(string $itemId, string $scenario)
{
parent::__construct($scenario);
$this->setItemId($itemId);
}

protected function setItemId(string $itemId): void
{
Assertion::typeIdentifier($itemId);

$this->itemId = $itemId;
}

protected function getCommandParameters(): array
{
$parameters = parent::getCommandParameters();

$parameters['item_id'] = $this->itemId;

return $parameters;
}
}
Loading

0 comments on commit c3de3a7

Please sign in to comment.