Files
portal/app/tests/Feature/Autopodbor/DeepStudyPhoneReverseTest.php
T
Дмитрий 9fce28fda8 feat(autopodbor): Яндекс-разворот по телефонам на шаге 2 (рядом с 2ГИС)
Для застройщиков, которые сидят только в Яндексе. По каждому номеру ищем в
Яндекс.Картах, но список рыхлый — поэтому КАЖДУЮ карточку открываем и оставляем
только те, где искомый номер реально есть. Это отсекает «Похожие места рядом»,
рекламу и рыхлые совпадения по бренду. Оба справочника объединяются
(CompositePhoneReverse), дубли по домену схлопываются.

Переиспользована готовая механика Яндекса: render-yandex-list.cjs (список) +
DirectoryParser::parseYandexCard (карточка). Браузерные адаптеры за интерфейсами
(YandexPhoneSearch/YandexCardReader) — логика TDD-ится на фейках; порог общей
линии считается по подтверждённым карточкам.

За отдельным тумблером AUTOPODBOR_PHONE_REVERSE_YANDEX (по умолчанию ВЫКЛ,
требует phone_reverse). Юнит 267/267, feature 150/150. План и находки —
docs/superpowers/{plans,findings}/2026-07-05-*.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 09:30:27 +03:00

143 lines
6.0 KiB
PHP

<?php
use App\Services\Autopodbor\Agent\Fetch\FetchedSite;
use App\Services\Autopodbor\Agent\Fetch\Fetcher;
use App\Services\Autopodbor\Agent\Fetch\XfetchClient;
use App\Services\Autopodbor\Agent\Study\DeepStudyCollector;
use App\Services\Autopodbor\Agent\Study\Fingerprint;
use App\Services\Autopodbor\Agent\Study\PhoneExpansionResult;
use App\Services\Autopodbor\Agent\Study\PhoneReverse;
use App\Services\Autopodbor\Agent\Study\SiteOpener;
use Illuminate\Support\Facades\Http;
it('за флагом phone_reverse вплетает находки разворота: сайт проекта и его телефон', function () {
config()->set('autopodbor.phone_reverse', true);
config()->set('services.exa', ['key' => 't', 'base_url' => 'https://api.exa.ai']);
Http::fake([
'api.exa.ai/*' => Http::response(['results' => [['url' => 'https://gk-dsk.ru/']]]),
'*' => Http::response(['response_body_base64' => '']),
]);
$siteFetcher = new class implements Fetcher
{
public function site(string $url): FetchedSite
{
return new FetchedSite($url, '<a href="tel:+73912273531">gk-dsk.ru</a>');
}
public function directory(string $url): array
{
return [];
}
};
$reverse = new class implements PhoneReverse
{
public function expand(string $gisSlug, array $seedPhones, array $seedDomains, string $yandexSlug = ''): PhoneExpansionResult
{
return new PhoneExpansionResult(
sites: [['domain' => 'zvezda-dsk.ru', 'site_url' => 'https://zvezda-dsk.ru', 'card_url' => 'https://2gis.ru/krasnoyarsk/firm/111']],
phones: [['digits' => '73912002288', 'url' => 'https://2gis.ru/krasnoyarsk/firm/111', 'source' => '2ГИС']],
);
}
};
$collector = new DeepStudyCollector(
xfetch: new XfetchClient(null),
opener: new SiteOpener($siteFetcher),
reverse: $reverse,
);
$fp = new Fingerprint('ДСК', 'gk-dsk.ru', 'Красноярск', '391', 'krasnoyarsk', false);
$rows = $collector->collect($fp);
$siteDomains = array_map(fn ($r) => $r['identifier'], array_filter($rows, fn ($r) => $r['signal_type'] === 'site'));
$callDigits = array_map(fn ($r) => $r['identifier'], array_filter($rows, fn ($r) => $r['signal_type'] === 'call'));
expect($siteDomains)->toContain('zvezda-dsk.ru')
->and($callDigits)->toContain('73912002288');
});
it('без флага phone_reverse разворот не зовётся (находок разворота нет)', function () {
config()->set('autopodbor.phone_reverse', false);
config()->set('services.exa', ['key' => 't', 'base_url' => 'https://api.exa.ai']);
Http::fake([
'api.exa.ai/*' => Http::response(['results' => []]),
'*' => Http::response(['response_body_base64' => '']),
]);
$siteFetcher = new class implements Fetcher
{
public function site(string $url): FetchedSite
{
return new FetchedSite($url, '');
}
public function directory(string $url): array
{
return [];
}
};
$reverse = new class implements PhoneReverse
{
public function expand(string $gisSlug, array $seedPhones, array $seedDomains, string $yandexSlug = ''): PhoneExpansionResult
{
return new PhoneExpansionResult(
sites: [['domain' => 'nemojet.ru', 'site_url' => 'https://nemojet.ru', 'card_url' => 'https://2gis.ru/x/firm/9']],
);
}
};
$collector = new DeepStudyCollector(
xfetch: new XfetchClient(null),
opener: new SiteOpener($siteFetcher),
reverse: $reverse,
);
$fp = new Fingerprint('ДСК', 'gk-dsk.ru', 'Красноярск', '391', 'krasnoyarsk', false);
$rows = $collector->collect($fp);
$siteDomains = array_map(fn ($r) => $r['identifier'], array_filter($rows, fn ($r) => $r['signal_type'] === 'site'));
expect($siteDomains)->not->toContain('nemojet.ru');
});
it('в разворот передаётся yandexSlug из отпечатка (для Яндекс-разворота)', function () {
config()->set('autopodbor.phone_reverse', true);
config()->set('services.exa', ['key' => 't', 'base_url' => 'https://api.exa.ai']);
\Illuminate\Support\Facades\Http::fake([
'api.exa.ai/*' => \Illuminate\Support\Facades\Http::response(['results' => [['url' => 'https://gk-dsk.ru/']]]),
'*' => \Illuminate\Support\Facades\Http::response(['response_body_base64' => '']),
]);
$siteFetcher = new class implements \App\Services\Autopodbor\Agent\Fetch\Fetcher
{
public function site(string $url): \App\Services\Autopodbor\Agent\Fetch\FetchedSite
{
return new \App\Services\Autopodbor\Agent\Fetch\FetchedSite($url, '<a href="tel:+73912273531">x</a>');
}
public function directory(string $url): array
{
return [];
}
};
$captured = (object) ['yandexSlug' => null];
$reverse = new class($captured) implements \App\Services\Autopodbor\Agent\Study\PhoneReverse
{
public function __construct(private object $cap) {}
public function expand(string $gisSlug, array $seedPhones, array $seedDomains, string $yandexSlug = ''): \App\Services\Autopodbor\Agent\Study\PhoneExpansionResult
{
$this->cap->yandexSlug = $yandexSlug;
return new \App\Services\Autopodbor\Agent\Study\PhoneExpansionResult;
}
};
$collector = new \App\Services\Autopodbor\Agent\Study\DeepStudyCollector(
xfetch: new \App\Services\Autopodbor\Agent\Fetch\XfetchClient(null),
opener: new \App\Services\Autopodbor\Agent\Study\SiteOpener($siteFetcher),
reverse: $reverse,
);
$fp = new \App\Services\Autopodbor\Agent\Study\Fingerprint('ДСК', 'gk-dsk.ru', 'Красноярск', '391', 'krasnoyarsk', false, '62/krasnoyarsk');
$collector->collect($fp);
expect($captured->yandexSlug)->toBe('62/krasnoyarsk');
});