feat(автоподбор): второй справочник канала А — Яндекс.Карты через локальный Playwright + слияние
Firecrawl Яндекс.Карты не рендерит (0-2 орг) — по §12.2 Яндекс берём локальным Playwright. render-yandex-list.cjs скроллит ленту результатов → 113 орг за ~18с (быстрее xfetch-2ГИС). YandexDirectory (граница) + PlaywrightYandexDirectory (живой, Process→node). Яндекс = имя+карточка (сайта в списке нет — только на карточке, не открываем). Оркестратор: канал А = 2ГИС(сайт)+Яндекс, слияние (mergeCompetitors union-find) схлопывает одного конкурента из обоих справочников в одну карточку с двумя directory_urls; сайт из 2ГИС. Провайдер подключает живой Яндекс. listingHtml → общий хелпер tests/Pest.php. Модуль 136 unit + 74 feature зелёные. За флагом; на проде не меняется. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ use App\Services\Autopodbor\Agent\Aggregator\AggregatorFilter;
|
||||
use App\Services\Autopodbor\Agent\ChannelA\CategoryListingParser;
|
||||
use App\Services\Autopodbor\Agent\ChannelA\CategoryScraper;
|
||||
use App\Services\Autopodbor\Agent\ChannelA\QueryAnalyzer;
|
||||
use App\Services\Autopodbor\Agent\ChannelA\YandexDirectory;
|
||||
use App\Services\Autopodbor\Agent\ChannelB\ChannelBSearch;
|
||||
use App\Services\Autopodbor\Agent\ChannelB\ExaSiteFinder;
|
||||
use App\Services\Autopodbor\Agent\ChannelB\ResearcherClient;
|
||||
@@ -77,11 +78,25 @@ function noAiAssembler(): FindCompetitorsAssembler
|
||||
);
|
||||
}
|
||||
|
||||
function leanEngine($pages, ResearcherClient $researcher, HttpFactory $http, array $queries): LiveFindCompetitors
|
||||
function fakeYandex(array $rows): YandexDirectory
|
||||
{
|
||||
return new class($rows) implements YandexDirectory
|
||||
{
|
||||
public function __construct(private array $rows) {}
|
||||
|
||||
public function collect(string $city, array $queries): array
|
||||
{
|
||||
return $this->rows;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function leanEngine($pages, ResearcherClient $researcher, HttpFactory $http, array $queries, array $yandexRows = []): LiveFindCompetitors
|
||||
{
|
||||
return new LiveFindCompetitors(
|
||||
fakeAnalyzer($queries),
|
||||
new CategoryScraper($pages, new CategoryListingParser, maxPages: 2),
|
||||
fakeYandex($yandexRows),
|
||||
new ChannelBSearch($researcher, new ResearcherParser),
|
||||
new ExaSiteFinder($http),
|
||||
noAiAssembler(),
|
||||
@@ -118,6 +133,34 @@ it('сквозной: канал А (2ГИС-список: имя+карточк
|
||||
->and($cm['site_url'])->toBe('carmoney.ru');
|
||||
});
|
||||
|
||||
it('слияние 2ГИС+Яндекс: один конкурент в обоих справочниках → одна карточка с двумя ссылками', function () {
|
||||
Config::set('services.exa.key', '');
|
||||
$http = app(HttpFactory::class);
|
||||
|
||||
$pages = stubPages([
|
||||
'2gis.ru/krasnoyarsk/search/' => listingHtml([[985690700540003, 'КрасЛомбард', 'kraslombard24.ru']]),
|
||||
]);
|
||||
// Яндекс дал того же КрасЛомбарда (без сайта) + новую фирму, которой нет в 2ГИС
|
||||
$yandex = [
|
||||
['name' => 'КрасЛомбард', 'card_url' => 'https://yandex.ru/maps/org/kraslombard/175852236692'],
|
||||
['name' => 'ЯрКомиссионка', 'card_url' => 'https://yandex.ru/maps/org/yarkomissionka/226908207223'],
|
||||
];
|
||||
|
||||
$res = leanEngine($pages, fakeResearcher(['[]']), $http, ['ломбард'], $yandex)
|
||||
->find(new FindCompetitorsRequest(29, [], ['ломбард', 'lkomega.ru'], false, 20));
|
||||
|
||||
$names = array_column($res->competitors, 'name');
|
||||
// КрасЛомбард — один раз (2ГИС+Яндекс слиты), плюс новая из Яндекса
|
||||
expect(array_count_values($names)['КрасЛомбард'])->toBe(1)
|
||||
->and($names)->toContain('ЯрКомиссионка');
|
||||
|
||||
// у слитого КрасЛомбарда — обе ссылки-справочники и сайт из 2ГИС
|
||||
$kl = collect($res->competitors)->firstWhere('name', 'КрасЛомбард');
|
||||
expect($kl['site_url'])->toBe('kraslombard24.ru')
|
||||
->and(collect($kl['directory_urls'])->contains(fn ($u) => str_contains($u, '2gis.ru')))->toBeTrue()
|
||||
->and(collect($kl['directory_urls'])->contains(fn ($u) => str_contains($u, 'yandex.ru')))->toBeTrue();
|
||||
});
|
||||
|
||||
it('канал В: имя без сайта в EXA выкидывается (нет якоря)', function () {
|
||||
Config::set('services.exa.key', 'k');
|
||||
$http = app(HttpFactory::class);
|
||||
|
||||
Reference in New Issue
Block a user