Skip to content

Commit

Permalink
Merge pull request #91 from adshares/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
m-pilarczyk authored Aug 7, 2023
2 parents 084483c + e2db65d commit d27a7f4
Show file tree
Hide file tree
Showing 10 changed files with 629 additions and 989 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.3.0] - 2023-08-07
### Added
- Direct deal support

## [1.2.3] - 2022-12-13
### Changed
- Find by scopes (multiple sizes)
Expand Down Expand Up @@ -86,7 +90,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.1.0] - 2019-04-19
Last python version

[Unreleased]: https://github.com/adshares/adselect/compare/v1.2.3...develop
[Unreleased]: https://github.com/adshares/adselect/compare/v1.3.0...develop
[1.3.0]: https://github.com/adshares/adselect/compare/v1.2.3...v1.3.0
[1.2.3]: https://github.com/adshares/adselect/compare/v1.2.2...v1.2.3
[1.2.2]: https://github.com/adshares/adselect/compare/v1.2.1...v1.2.2
[1.2.1]: https://github.com/adshares/adselect/compare/v1.2.0...v1.2.1
Expand Down
1,086 changes: 468 additions & 618 deletions composer.lock

Large diffs are not rendered by default.

11 changes: 2 additions & 9 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
bootstrap="tests/bootstrap.php"
convertDeprecationsToExceptions="false"
>
<php>
<ini name="display_errors" value="1" />
Expand All @@ -31,18 +30,12 @@
</include>
</coverage>

<extensions>
<extension class="DAMA\DoctrineTestBundle\PHPUnit\PHPUnitExtension"/>
</extensions>

<listeners>
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
</listeners>

<!-- Run `composer require symfony/panther` before enabling this extension -->
<!--
<extensions>
<extension class="Symfony\Component\Panther\ServerExtension" />
<extension class="DAMA\DoctrineTestBundle\PHPUnit\PHPUnitExtension" />
</extensions>
-->

</phpunit>
2 changes: 1 addition & 1 deletion sonar-project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ sonar.host.url=https://sonarcloud.io
sonar.organization=adshares-github
sonar.projectKey=adshares-adselect
sonar.projectName=Adshares AdSelect
sonar.projectVersion=1.2
sonar.projectVersion=1.3

# =====================================================
# Meta-data for the project
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

/**
* @phpcs:disable Generic.Files.LineLength.TooLong
*/

declare(strict_types=1);

namespace App\Infrastructure\ElasticSearch\QueryBuilder;

class DirectDealQueryBuilder
{
private const SCRIPT_SCORE = <<<PAINLESS
return Math.round(
100000.0
* (params.last_seen_banners.containsKey(doc._id[0]) ? (params.last_seen_banners[doc._id[0]]) : 1)
* (params.last_seen_campaigns.containsKey(doc['campaign_id'][0]) ? (params.last_seen_campaigns[doc['campaign_id'][0]]) : 1)
)
* 100000
+ Math.random();
PAINLESS;

private QueryInterface $query;
private array $userHistory;

public function __construct(QueryInterface $query, array $userHistory)
{
$this->query = $query;
$this->userHistory = $userHistory;
}

public function build(): array
{
return [
'function_score' => [
'boost_mode' => 'replace',
'query' => $this->query->build(),
'script_score' => [
'script' => [
'lang' => 'painless',
'params' => [
'last_seen_banners' => (object)$this->userHistory['banners'],
'last_seen_campaigns' => (object)$this->userHistory['campaigns'],
],
'source' => self::SCRIPT_SCORE,
],
],
]
];
}
}
17 changes: 7 additions & 10 deletions src/Infrastructure/ElasticSearch/QueryBuilder/ExpQueryBuilder.php
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
<?php

/**
* @phpcs:disable Generic.Files.LineLength.TooLong
*/

declare(strict_types=1);

namespace App\Infrastructure\ElasticSearch\QueryBuilder;

class ExpQueryBuilder
{
private QueryInterface $query;

// see: Weighted Random Sampling (2005; Efraimidis, Spirakis)
// http://utopia.duth.gr/~pefraimi/research/data/2007EncOfAlg.pdf
// encode score and rpm in one number. 5 (3+2) significant digits for rpm
private const SCORE_SCRIPT
= <<<PAINLESS
double real_rpm = _score % 1000.0;
Expand All @@ -20,6 +17,8 @@ class ExpQueryBuilder
return Math.round(1000.0 * weight) * 100000 + Math.round(real_rpm * 100) + Math.random();
PAINLESS;

private QueryInterface $query;

public function __construct(QueryInterface $query)
{
$this->query = $query;
Expand All @@ -29,14 +28,12 @@ public function build(): array
{
return [
'function_score' => [
'query' => $this->query->build(),
'boost_mode' => 'replace',
'query' => $this->query->build(),
'script_score' => [
'script' => [
'lang' => 'painless',
// see: Weighted Random Sampling (2005; Efraimidis, Spirakis) http://utopia.duth.gr/~pefraimi/research/data/2007EncOfAlg.pdf
// encode score and rpm in one number. 5 (3+2) significant digits for rpm
'source' => self::SCORE_SCRIPT
'source' => self::SCORE_SCRIPT,
],
],
],
Expand Down
55 changes: 30 additions & 25 deletions src/Infrastructure/ElasticSearch/QueryBuilder/QueryBuilder.php
Original file line number Diff line number Diff line change
@@ -1,28 +1,18 @@
<?php

/**
* @phpcs:disable Generic.Files.LineLength.TooLong
*/

declare(strict_types=1);

namespace App\Infrastructure\ElasticSearch\QueryBuilder;

class QueryBuilder
{
private array $userHistory;
private QueryInterface $query;
private float $minCpm;

public function __construct(QueryInterface $query, float $minCpm, array $userHistory = [])
{
$this->userHistory = $userHistory;
$this->query = $query;
$this->minCpm = $minCpm;
}

public function build(): array
{
// randomize if no stats at all
// encode score and rpm in one number. 5 (3+2) significant digits for rpm
$scriptScore
= <<<PAINLESS
// randomize if no stats at all
// encode score and rpm in one number. 5 (3+2) significant digits for rpm
private const SCRIPT_SCORE = <<<PAINLESS
double real_rpm = _score % 1000.0;
if (params.min_rpm > real_rpm) {
return Math.random();
Expand All @@ -31,26 +21,41 @@ public function build(): array
100.0
* real_rpm
* Math.random()
* (params.last_seen.containsKey(doc._id[0]) ? (params.last_seen[doc._id[0]]) : 1)
* (params.last_seen_banners.containsKey(doc._id[0]) ? (params.last_seen_banners[doc._id[0]]) : 1)
* (params.last_seen_campaigns.containsKey(doc['campaign_id'][0]) ? (params.last_seen_campaigns[doc['campaign_id'][0]]) : 1)
)
* 100000
+ Math.round(real_rpm * 100)
+ Math.random();
PAINLESS;

private QueryInterface $query;
private float $minCpm;
private array $userHistory;

public function __construct(QueryInterface $query, float $minCpm, array $userHistory = [])
{
$this->query = $query;
$this->minCpm = $minCpm;
$this->userHistory = $userHistory;
}

public function build(): array
{
return [
'function_score' => [
'boost_mode' => 'replace',
'query' => $this->query->build(),
'script_score' => [
"script" => [
"lang" => "painless",
"params" => [
"last_seen" => (object)$this->userHistory,
"min_rpm" => $this->minCpm,
'script' => [
'lang' => 'painless',
'params' => [
'last_seen_banners' => (object)$this->userHistory['banners'],
'last_seen_campaigns' => (object)$this->userHistory['campaigns'],
'min_rpm' => $this->minCpm,
],
"source" => $scriptScore,
]
'source' => self::SCRIPT_SCORE,
],
],
]
];
Expand Down
66 changes: 35 additions & 31 deletions src/Infrastructure/ElasticSearch/Service/BannerFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use App\Infrastructure\ElasticSearch\Client;
use App\Infrastructure\ElasticSearch\Mapping\BannerIndex;
use App\Infrastructure\ElasticSearch\QueryBuilder\BaseQuery;
use App\Infrastructure\ElasticSearch\QueryBuilder\DirectDealQueryBuilder;
use App\Infrastructure\ElasticSearch\QueryBuilder\ExpQueryBuilder;
use App\Infrastructure\ElasticSearch\QueryBuilder\QueryBuilder;
use Psr\Log\LoggerInterface;
Expand All @@ -22,6 +23,7 @@ class BannerFinder implements BannerFinderInterface
private const HISTORY_APC_KEY_PREFIX = 'Adselect.UserHistory';
private const HISTORY_ENTRY_TIME = 0;
private const HISTORY_ENTRY_BANNER_ID = 1;
private const HISTORY_ENTRY_CAMPAIGN_ID = 2;
private const HISTORY_MAXAGE = 3600 * 3;
private const HISTORY_MAXENTRIES = 50;

Expand Down Expand Up @@ -63,35 +65,28 @@ public function find(
],
];

$chance = (mt_rand(0, 999) / 1000);

if ($chance < $this->experimentChance) {
$this->logger->debug(
sprintf(
'[BANNER FINDER] experiment < %s',
$this->experimentChance
)
);
$queryBuilder = new ExpQueryBuilder($query);
$isDirectDeal = (bool)$queryDto->getZoneOption('direct_deal', false);
if ($isDirectDeal) {
$this->logger->debug('[BANNER FINDER] direct deal');
$queryBuilder = new DirectDealQueryBuilder($query, $this->getSeenOrder($userHistory));
} else {
$queryBuilder = new QueryBuilder(
$query,
(float)$queryDto->getZoneOption('min_cpm', 0.0),
$this->getSeenOrder($userHistory)
);
$this->logger->debug('[BANNER FINDER] regular');
$chance = (mt_rand(0, 999) / 1000);
if ($chance < $this->experimentChance) {
$this->logger->debug(sprintf('[BANNER FINDER] experiment %s < %s', $chance, $this->experimentChance));
$queryBuilder = new ExpQueryBuilder($query);
} else {
$this->logger->debug(sprintf('[BANNER FINDER] regular %s >= %s', $chance, $this->experimentChance));
$queryBuilder = new QueryBuilder(
$query,
(float)$queryDto->getZoneOption('min_cpm', 0.0),
$this->getSeenOrder($userHistory),
);
}
}

$params['body']['query'] = $queryBuilder->build();

$this->logger->debug(
sprintf(
'[BANNER FINDER] sending a query: %s %s %s',
$chance,
$this->experimentChance,
json_encode($params)
)
);
$this->logger->debug(sprintf('[BANNER FINDER] sending a query: %s', json_encode($params)));

$response = $this->client->search($params);

Expand Down Expand Up @@ -133,14 +128,22 @@ public function find(

private function getSeenOrder(array $userHistory): array
{
$seen = [];
$seen = [
'banners' => [],
'campaigns' => [],
];

foreach (array_reverse($userHistory) as $id => $entry) {
$mod = ($id ** 2) / (($id + 1) ** 2);
if (!isset($seen[$entry[self::HISTORY_ENTRY_BANNER_ID]])) {
$seen[$entry[self::HISTORY_ENTRY_BANNER_ID]] = $mod;
$mod = sqrt(($id ** 2) / (($id + 1) ** 2));
if (isset($seen['banners'][$entry[self::HISTORY_ENTRY_BANNER_ID]])) {
$seen['banners'][$entry[self::HISTORY_ENTRY_BANNER_ID]] *= $mod;
} else {
$seen['banners'][$entry[self::HISTORY_ENTRY_BANNER_ID]] = $mod;
}
if (isset($seen['campaigns'][$entry[self::HISTORY_ENTRY_CAMPAIGN_ID]])) {
$seen['campaigns'][$entry[self::HISTORY_ENTRY_CAMPAIGN_ID]] *= $mod;
} else {
$seen[$entry[self::HISTORY_ENTRY_BANNER_ID]] *= $mod;
$seen['campaigns'][$entry[self::HISTORY_ENTRY_CAMPAIGN_ID]] = $mod;
}
}
$this->logger->debug(sprintf('[BANNER FINDER] seen: %s', json_encode($seen)));
Expand Down Expand Up @@ -199,8 +202,9 @@ private function updateUserHistory(
// It can be implemented only when we return one banner. Otherwise, we do not know which one is displayed.
if ($collection->count() > 0) {
$history[] = [
self::HISTORY_ENTRY_TIME => $this->timeService->getDateTime()->getTimestamp(),
self::HISTORY_ENTRY_BANNER_ID => $collection[0]->getBannerId(),
self::HISTORY_ENTRY_TIME => $this->timeService->getDateTime()->getTimestamp(),
self::HISTORY_ENTRY_BANNER_ID => $collection[0]->getBannerId(),
self::HISTORY_ENTRY_CAMPAIGN_ID => $collection[0]->getCampaignId(),
];
}
}
Expand Down
Loading

0 comments on commit d27a7f4

Please sign in to comment.