/home/alixis5/integrate.sellofinance.com/tests/Unit/Services
Edit: /home/alixis5/integrate.sellofinance.com/tests/Unit/Services/MeritRankerTest.php (5290B)
ranker = new MeritRanker();
}
public function test_rate_competitiveness_at_average_scores_zero(): void
{
$r = $this->ranker->rateCompetitiveness(productMidRate: 5.0, categoryAvgRate: 5.0);
$this->assertSame(0.0, $r);
}
public function test_rate_competitiveness_below_average_scores_positive(): void
{
// categoryAvg = 5.0; productMid = 4.0 → 20% below → 0.2
$r = $this->ranker->rateCompetitiveness(productMidRate: 4.0, categoryAvgRate: 5.0);
$this->assertEqualsWithDelta(0.2, $r, 1e-6);
}
public function test_rate_competitiveness_category_avg_zero_returns_one(): void
{
$r = $this->ranker->rateCompetitiveness(productMidRate: 0.0, categoryAvgRate: 0.0);
$this->assertSame(1.0, $r);
}
public function test_eligibility_breadth_zero_requirements_scores_high(): void
{
// Product has minSalary=0 in group [0..10000], minScore=0 in group [0..700]
$b = $this->ranker->eligibilityBreadth(
minSalary: 0, salaryMin: 0, salaryMax: 10000,
minScore: 0, scoreMin: 0, scoreMax: 700,
);
$this->assertEqualsWithDelta(1.0, $b, 1e-6);
}
public function test_eligibility_breadth_max_requirements_scores_zero(): void
{
$b = $this->ranker->eligibilityBreadth(
minSalary: 10000, salaryMin: 0, salaryMax: 10000,
minScore: 700, scoreMin: 0, scoreMax: 700,
);
$this->assertEqualsWithDelta(0.0, $b, 1e-6);
}
public function test_eligibility_breadth_returns_half_when_group_flat(): void
{
// All products in group share same salary/score → no signal
$b = $this->ranker->eligibilityBreadth(
minSalary: 5000, salaryMin: 5000, salaryMax: 5000,
minScore: 600, scoreMin: 600, scoreMax: 600,
);
$this->assertEqualsWithDelta(0.5, $b, 1e-6);
}
public function test_freshness_decays_exponentially(): void
{
$this->assertEqualsWithDelta(1.0, $this->ranker->freshness(0), 1e-6);
$this->assertEqualsWithDelta(0.72, $this->ranker->freshness(30), 0.01);
$this->assertEqualsWithDelta(0.37, $this->ranker->freshness(90), 0.01);
}
public function test_score_combines_three_weighted_components(): void
{
// Weights 0.5 / 0.3 / 0.2 — all inputs = 1.0 → score = 1.0
$s = $this->ranker->score(rate: 1.0, eligibility: 1.0, freshness: 1.0);
$this->assertEqualsWithDelta(1.0, $s, 1e-6);
// All inputs = 0 → 0
$this->assertEqualsWithDelta(0.0, $this->ranker->score(0.0, 0.0, 0.0), 1e-6);
// Rate=1 only → 0.5
$this->assertEqualsWithDelta(0.5, $this->ranker->score(1.0, 0.0, 0.0), 1e-6);
}
public function test_diversity_cap_limits_per_institution(): void
{
$products = [];
for ($i = 0; $i < 10; $i++) {
$products[] = [
'id' => "p{$i}", 'accountId' => 'bank-A',
'_score' => 1.0 - ($i * 0.01),
'modifiedAt' => '2026-04-22T12:00:00+00:00',
];
}
for ($i = 0; $i < 3; $i++) {
$products[] = [
'id' => "pB{$i}", 'accountId' => 'bank-B',
'_score' => 0.5 - ($i * 0.01),
'modifiedAt' => '2026-04-22T12:00:00+00:00',
];
}
$top = $this->ranker->applyDiversityCap($products, limitPerInstitution: 3, finalLimit: 5);
$this->assertCount(5, $top);
$byBank = array_count_values(array_column($top, 'accountId'));
$this->assertSame(3, $byBank['bank-A']);
$this->assertSame(2, $byBank['bank-B']);
}
public function test_rank_full_pipeline_returns_top_n(): void
{
$products = [
// Cheapest rate, zero requirements — should win
['id' => 'winner', 'productType' => 'personalLoan', 'categoryId' => 'cat-1', 'accountId' => 'bank-A',
'profitRateMin' => 1.0, 'profitRateMax' => 2.0, 'minSalary' => 0, 'minCreditScore' => 0,
'createdAt' => '2026-04-22T00:00:00+00:00', 'modifiedAt' => '2026-04-22T00:00:00+00:00'],
['id' => 'avg', 'productType' => 'personalLoan', 'categoryId' => 'cat-1', 'accountId' => 'bank-A',
'profitRateMin' => 5.0, 'profitRateMax' => 6.0, 'minSalary' => 5000, 'minCreditScore' => 600,
'createdAt' => '2026-02-01T00:00:00+00:00', 'modifiedAt' => '2026-02-01T00:00:00+00:00'],
['id' => 'expensive','productType' => 'personalLoan', 'categoryId' => 'cat-1', 'accountId' => 'bank-A',
'profitRateMin' => 9.0, 'profitRateMax' => 10.0,'minSalary' => 10000,'minCreditScore' => 700,
'createdAt' => '2026-01-01T00:00:00+00:00', 'modifiedAt' => '2026-01-01T00:00:00+00:00'],
];
$top = $this->ranker->rank($products, now: new \DateTimeImmutable('2026-04-22T12:00:00+00:00'), limit: 3);
$this->assertSame('winner', $top[0]['id']);
}
}