adc3590ab6
Найдено живым прогоном по нише «ломбард/Красноярск»: 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>
169 lines
6.6 KiB
PHP
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([]);
|
|
});
|