0131ecfc47
Качество. Привязанные клиентом карточки 2ГИС/Яндекс теперь используются как «земля» и якорь холдинга (раньше выбрасывались). Слепой поиск по имени берётся только при наличии якоря; AnchorGate: пустой якорь → drop (было keep-всё — корень шума). Без сайта чужие фирмы по широкому имени («Притяжение» → стоматология, салоны, эротический магазин) больше не попадают в карточку конкурента. Доказано на живых данных (Сибагропромстрой): свежий полный сбор — 0 салонов, только номера застройщика. Скорость (за флагом AUTOPODBOR_PHONE_REVERSE_PARALLEL, ВЫКЛ по умолчанию). ParallelPhoneReverse крутит 2ГИС- и Яндекс-разворот ОДНОВРЕМЕННО (итог по времени = max, не сумма; оба источника отрабатывают полностью — ничего не режется). Сверка последовательно vs параллельно: данные один в один, потерь нет. Таймаут подпроцесса 600с (драйвер process иначе берёт дефолт Symfony 60с и убивал разворот). Тесты: AnchorGate (пустой якорь → drop; телефон-якорь режет чужое), якорь привязанной карточки режет салоны, ParallelPhoneReverse (слияние на sync-драйвере). Весь Autopodbor 464/464, Pint чист. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
115 lines
4.2 KiB
PHP
115 lines
4.2 KiB
PHP
<?php
|
|
|
|
use App\Services\Autopodbor\Agent\Extract\TwoGisReader;
|
|
use App\Services\Autopodbor\Agent\Fetch\BatchPageFetcher;
|
|
use App\Services\Autopodbor\Agent\Fetch\DirectoryCard;
|
|
use App\Services\Autopodbor\Agent\Fetch\FetchedSite;
|
|
use App\Services\Autopodbor\Agent\Fetch\Fetcher;
|
|
use App\Services\Autopodbor\Agent\Study\DeepStudyCollector;
|
|
use App\Services\Autopodbor\Agent\Study\Fingerprint;
|
|
use App\Services\Autopodbor\Agent\Study\SiteOpener;
|
|
|
|
// Земля: клиент привязал карточку 2ГИС с номером 002005 → он якорь и источник. Слепой поиск по
|
|
// широкому имени «Притяжение» вернул чужой салон (303303) — он должен отсеяться (не бьётся с якорем).
|
|
// Доказано на живых данных (стоматология/салоны попадали в карточку конкурента) — 2026-07-06.
|
|
|
|
const CLIENT_CARD = 'https://2gis.ru/krasnoyarsk/firm/CLIENT';
|
|
const FOREIGN_FIRM = 'https://2gis.ru/krasnoyarsk/firm/FOREIGN';
|
|
|
|
function fakeXfetch(): BatchPageFetcher
|
|
{
|
|
return new class implements BatchPageFetcher
|
|
{
|
|
public function html(string $url): string
|
|
{
|
|
if ($url === CLIENT_CARD) {
|
|
return 'CLIENT';
|
|
}
|
|
if (str_contains($url, '/search/')) {
|
|
return 'SEARCH';
|
|
}
|
|
if (str_contains($url, 'FOREIGN')) {
|
|
return 'FOREIGN';
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
public function htmlBatch(array $urls): array
|
|
{
|
|
$out = [];
|
|
foreach ($urls as $u) {
|
|
$out[$u] = $this->html($u);
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
};
|
|
}
|
|
|
|
function fakeReader(): TwoGisReader
|
|
{
|
|
return new class implements TwoGisReader
|
|
{
|
|
public function listing(string $payload): array
|
|
{
|
|
return [];
|
|
}
|
|
|
|
public function card(string $payload, string $url): array
|
|
{
|
|
if ($payload === 'CLIENT') {
|
|
return [new DirectoryCard(number: '8 (391) 200-20-05', office: null, url: $url, source: '2ГИС', siteUrl: null, description: null)];
|
|
}
|
|
if ($payload === 'FOREIGN') {
|
|
return [new DirectoryCard(number: '8 (391) 230-33-03', office: null, url: $url, source: '2ГИС', siteUrl: 'http://salon.ru', description: 'Парикмахерские')];
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
public function branchList(string $payload): array
|
|
{
|
|
return $payload === 'SEARCH' ? ['/krasnoyarsk/firm/FOREIGN'] : [];
|
|
}
|
|
};
|
|
}
|
|
|
|
function emptyOpener(): SiteOpener
|
|
{
|
|
$fetcher = new class implements Fetcher
|
|
{
|
|
public function site(string $url): FetchedSite
|
|
{
|
|
return new FetchedSite($url, '');
|
|
}
|
|
|
|
public function directory(string $url): array
|
|
{
|
|
return [];
|
|
}
|
|
};
|
|
|
|
return new SiteOpener($fetcher);
|
|
}
|
|
|
|
it('привязанная карточка = якорь: её номер в источниках, чужой салон из слепого поиска отсеян', function () {
|
|
$collector = new DeepStudyCollector(xfetch: fakeXfetch(), opener: emptyOpener(), reader: fakeReader());
|
|
$fp = new Fingerprint('Притяжение', '', 'Красноярск', '391', 'krasnoyarsk', false, '', [CLIENT_CARD]);
|
|
|
|
$calls = array_map(
|
|
fn ($r) => $r['identifier'],
|
|
array_values(array_filter($collector->collect($fp), fn ($r) => $r['signal_type'] === 'call')),
|
|
);
|
|
|
|
expect($calls)->toContain('73912002005') // номер с привязанной карточки — есть
|
|
->and($calls)->not->toContain('73912303303'); // салон из слепого поиска — отсеян
|
|
});
|
|
|
|
it('нет якоря совсем (без сайта и без карточек) — слепой поиск не берётся, пусто', function () {
|
|
$collector = new DeepStudyCollector(xfetch: fakeXfetch(), opener: emptyOpener(), reader: fakeReader());
|
|
$fp = new Fingerprint('Притяжение', '', 'Красноярск', '391', 'krasnoyarsk', false, '', []);
|
|
|
|
expect($collector->collect($fp))->toBe([]);
|
|
});
|