Files
portal/app/tests/Unit/Autopodbor/LiveFindCompetitorsTest.php
T
Дмитрий adc3590ab6 fix(автоподбор): живой резолв Яндекса (город в заголовке не на фикс. месте) + ретрай флакующей выдачи
Найдено живым прогоном по нише «ломбард/Красноярск»:
1) Яндекс-заголовок «Имя, рубрика, ГОРОД, улица, дом» — город НЕ последний сегмент; брал «дом»
   как город → все карточки отбраковывались. Теперь YandexResolver: имя=первый сегмент, город —
   по НАЛИЧИЮ в заголовке (DirectoryFields::titleName/titleHasCity). 2ГИС (город последний) — без изменений.
2) ResolvingAgent (ручной resolveByName) теперь берёт ГОРОД центра субъекта (RegionCity), не имя региона.
3) Страница выдачи 2ГИС/Яндекс флакует (капча/пустой layout) → LiveFindCompetitors повторяет поиск,
   пока не появятся фирмы (searchRetries).

Тесты: модуль Автоподбора unit 99/99 + live 4/4; Pint чисто.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 18:44:27 +03:00

169 lines
6.6 KiB
PHP

<?php
declare(strict_types=1);
use App\Services\Autopodbor\Agent\Aggregator\AggregatorClassifier;
use App\Services\Autopodbor\Agent\Aggregator\AggregatorFilter;
use App\Services\Autopodbor\Agent\Dto\FindCompetitorsRequest;
use App\Services\Autopodbor\Agent\Fetch\PageFetcher;
use App\Services\Autopodbor\Agent\FindCompetitorsAssembler;
use App\Services\Autopodbor\Agent\LiveFindCompetitors;
use App\Services\Autopodbor\Agent\Resolve\TwoGisResolver;
use App\Services\Autopodbor\Agent\Resolve\YandexResolver;
use App\Services\Autopodbor\Agent\Search\SearchResultsParser;
use App\Services\Autopodbor\Agent\Similarity\Embedder;
use App\Services\Autopodbor\Agent\Similarity\EmbeddingRelevance;
use App\Services\Autopodbor\AutopodborDedup;
use App\Services\Autopodbor\AutopodborNormalizer;
// stubPages()/autopodborFixture() — глобальные хелперы из tests/Pest.php.
function liveEngine(): LiveFindCompetitors
{
$nullClassifier = new class implements AggregatorClassifier
{
public function isAggregator(string $name, ?string $siteUrl, ?string $description): ?bool
{
return null; // без ИИ — никого не выкидываем
}
};
$zeroEmbedder = new class implements Embedder
{
public function embed(array $texts): array
{
return array_map(fn () => [0.0], $texts);
}
};
$assembler = new FindCompetitorsAssembler(
new AggregatorFilter($nullClassifier),
new AutopodborDedup(new AutopodborNormalizer),
new EmbeddingRelevance($zeroEmbedder),
);
return new LiveFindCompetitors(
stubPages([
// выдача
'2gis.ru/krasnoyarsk/search/' => autopodborFixture('2gis-search-kraslombard.html'),
'maps/?text=' => autopodborFixture('yandex-search-kraslombard.html'),
// карточки
'/firm/' => autopodborFixture('2gis-firm-kraslombard.html'),
'/maps/org/' => autopodborFixture('yandex-org-kraslombard.html'),
]),
new SearchResultsParser,
new TwoGisResolver,
new YandexResolver,
$assembler,
);
}
it('живой поиск по нише → настоящий конкурент, склеенный из 2ГИС и Яндекса', function () {
$res = liveEngine()->find(new FindCompetitorsRequest(
regionCode: 29, // Красноярский край → город Красноярск, слаг krasnoyarsk
examples: [],
aboutSelf: ['ломбард', 'moy-lombard.ru'], // ниша + ВАШ сайт (другой — себя НЕ вычитаем)
includeFederal: true,
maxCompetitors: 20,
));
$names = array_column($res->competitors, 'name');
expect($names)->toContain('КрасЛомбард'); // найден живым поиском по нише
$kl = collect($res->competitors)->firstWhere('name', 'КрасЛомбард');
expect($kl['site_url'])->toBe('http://kraslombard24.ru');
// склейка одной фирмы из 2ГИС + Яндекс: в «где нашли» обе ссылки справочников
expect(collect($kl['directory_urls'])->contains(fn (string $u): bool => str_contains($u, '/firm/')))->toBeTrue();
expect(collect($kl['directory_urls'])->contains(fn (string $u): bool => str_contains($u, '/maps/org/')))->toBeTrue();
});
it('свой сайт вычитается из конкурентов', function () {
$res = liveEngine()->find(new FindCompetitorsRequest(
regionCode: 29,
examples: [],
aboutSelf: ['ломбард', 'kraslombard24.ru'], // теперь КрасЛомбард = это «мы»
includeFederal: true,
maxCompetitors: 20,
));
expect(array_column($res->competitors, 'name'))->not->toContain('КрасЛомбард');
});
it('повторяет флакующую выдачу 2ГИС, пока не появятся фирмы', function () {
// первый запрос страницы 2ГИС-поиска — пусто (капча/флак), второй — реальная выдача
$pages = new class implements PageFetcher
{
private int $searchHits = 0;
public function html(string $url): string
{
if (str_contains($url, '/search/')) {
$this->searchHits++;
return $this->searchHits >= 2 ? autopodborFixture('2gis-search-kraslombard.html') : '';
}
if (str_contains($url, '/firm/')) {
return autopodborFixture('2gis-firm-kraslombard.html');
}
return ''; // Яндекс пропускаем
}
};
$nullClassifier = new class implements AggregatorClassifier
{
public function isAggregator(string $name, ?string $siteUrl, ?string $description): ?bool
{
return null;
}
};
$zeroEmbedder = new class implements Embedder
{
public function embed(array $texts): array
{
return array_map(fn () => [0.0], $texts);
}
};
$engine = new LiveFindCompetitors(
$pages,
new SearchResultsParser,
new TwoGisResolver,
new YandexResolver,
new FindCompetitorsAssembler(
new AggregatorFilter($nullClassifier),
new AutopodborDedup(new AutopodborNormalizer),
new EmbeddingRelevance($zeroEmbedder),
),
);
$res = $engine->find(new FindCompetitorsRequest(29, [], ['ломбард', 'moy.ru'], true, 20));
expect(array_column($res->competitors, 'name'))->toContain('КрасЛомбард');
});
it('пустая выдача → пустой список, без падения', function () {
$engine = new LiveFindCompetitors(
stubPages([]), // на всё ''
new SearchResultsParser,
new TwoGisResolver,
new YandexResolver,
new FindCompetitorsAssembler(
new AggregatorFilter(new class implements AggregatorClassifier
{
public function isAggregator(string $name, ?string $siteUrl, ?string $description): ?bool
{
return null;
}
}),
new AutopodborDedup(new AutopodborNormalizer),
new EmbeddingRelevance(new class implements Embedder
{
public function embed(array $texts): array
{
return array_map(fn () => [0.0], $texts);
}
}),
),
);
$res = $engine->find(new FindCompetitorsRequest(29, [], ['ничего', 'self.ru'], false, 10));
expect($res->competitors)->toBe([]);
});