Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 3
CRAP
78.57% covered (warning)
78.57%
33 / 42
PQScore
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 3
7.48
78.57% covered (warning)
78.57%
33 / 42
 getRequiredFields
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 score
0.00% covered (danger)
0.00%
0 / 1
3.05
81.82% covered (warning)
81.82%
9 / 11
 explain
0.00% covered (danger)
0.00%
0 / 1
3.07
80.00% covered (warning)
80.00%
24 / 30
<?php
namespace CirrusSearch\BuildDocument\Completion;
/**
 * Score that combines QualityScore and the pageviews statistics (popularity)
 */
class PQScore extends QualityScore {
    private const QSCORE_WEIGHT = 1;
    private const POPULARITY_WEIGHT = 0.4;
    // 0.04% of the total page views is the max we accept
    // @todo: tested on enwiki values only
    private const POPULARITY_MAX = 0.0004;
    /**
     * @return string[]
     */
    public function getRequiredFields() {
        return array_merge( parent::getRequiredFields(), [ 'popularity_score' ] );
    }
    /**
     * @param array $doc
     * @return int
     */
    public function score( array $doc ) {
        $score = $this->intermediateScore( $doc ) * self::QSCORE_WEIGHT;
        $pop = $doc['popularity_score'] ?? 0;
        if ( $pop > self::POPULARITY_MAX ) {
            $pop = 1;
        } else {
            $logBase = 1 + self::POPULARITY_MAX * $this->maxDocs;
            // log₁(x) is undefined
            if ( $logBase > 1 ) {
                // @fixme: rough log scale by using maxDocs...
                $pop = log( 1 + ( $pop * $this->maxDocs ), $logBase );
            } else {
                $pop = 0;
            }
        }
        $score += $pop * self::POPULARITY_WEIGHT;
        $score /= self::QSCORE_WEIGHT + self::POPULARITY_WEIGHT;
        return intval( $score * self::SCORE_RANGE );
    }
    public function explain( array $doc ) {
        $qualityExplain = $this->intermediateExplain( $doc );
        $pop = $doc['popularity_score'] ?? 0;
        if ( $pop > self::POPULARITY_MAX ) {
            $popExplain = [
                'value' => 1,
                'description' => 'pop > max_popularity, pop = ' . $pop .
                                 ', max_popularity' . self::POPULARITY_MAX,
            ];
        } else {
            $popLogBaseExplain = [
                'value' => 1 + self::POPULARITY_MAX * $this->maxDocs,
                'description' => '1+popularity_max*max_docs; popularity_max = ' . self::POPULARITY_MAX .
                                 ', max_docs = ' . $this->maxDocs,
            ];
            if ( $popLogBaseExplain['value'] > 1 ) {
                $popExplain = [
                    'value' => log(
                        1 + ( min( $pop, self::POPULARITY_MAX ) * $this->maxDocs ), $popLogBaseExplain['value']
                    ),
                    'description' => "log(1+(min(popularity,popularity_max)*max_docs), pop_logbase); popularity = $pop" .
                                     "popularity_max = " . self::POPULARITY_MAX . ", max_docs = {$this->maxDocs}" .
                                     "pop_logbase = {$popLogBaseExplain['value']}",
                    'details' => [ 'pop_logbase' => $popLogBaseExplain ]
                ];
            } else {
                $popExplain = [
                    'value' => 0,
                    'description' => 'log base 1 is undefined',
                    'details' => [ 'pop_logbase' => $popLogBaseExplain ]
                ];
            }
        }
        $totalW = self::QSCORE_WEIGHT + self::POPULARITY_WEIGHT;
        $wPop = $this->explainWeight( $popExplain, self::POPULARITY_WEIGHT, $totalW, 'popularity' );
        $wQua = $this->explainWeight( $qualityExplain, self::QSCORE_WEIGHT, $totalW, 'quality' );
        $details = [
            'popularity_weighted' => $wPop,
            'page_quality' => $wQua,
        ];
        $innerExp = [
            'value' => $wPop['value'] + $wQua['value'],
            'description' => "Weighted sum of doc quality score and popularity",
            'details' => $details
        ];
        return [
            'value' => (int)( $innerExp['value'] * self::SCORE_RANGE ),
            'description' => 'Convert to an integer score: ' . $innerExp['value'] . ' * ' . self::SCORE_RANGE,
            'details' => [ $innerExp ]
        ];
    }
}