diff --git a/app/app/Services/Autopodbor/Agent/Study/PhoneReverseExpander.php b/app/app/Services/Autopodbor/Agent/Study/PhoneReverseExpander.php index 5e817524..94fdc6e0 100644 --- a/app/app/Services/Autopodbor/Agent/Study/PhoneReverseExpander.php +++ b/app/app/Services/Autopodbor/Agent/Study/PhoneReverseExpander.php @@ -28,6 +28,7 @@ final class PhoneReverseExpander implements PhoneReverse private AutopodborNormalizer $norm = new AutopodborNormalizer, private int $crowdedCap = 4, private int $maxFirms = 40, + private int $maxDepth = 1, ) {} public function expand(string $gisSlug, array $seedPhones, array $seedDomains, string $yandexSlug = ''): PhoneExpansionResult @@ -49,7 +50,7 @@ final class PhoneReverseExpander implements PhoneReverse $d = $this->norm->phone((string) $p); if (strlen($d) === self::PHONE_DIGITS && ! isset($seenPhones[$d])) { $seenPhones[$d] = true; - $queue[] = $d; + $queue[] = [$d, 1]; } } @@ -59,7 +60,7 @@ final class PhoneReverseExpander implements PhoneReverse $phones = []; while ($queue !== [] && $firmsOpened < $this->maxFirms) { - $phone = array_shift($queue); + [$phone, $depth] = array_shift($queue); foreach ($this->firmUrlsFor($gisSlug, $phone) as $firmUrl) { if (isset($seenFirms[$firmUrl])) { continue; @@ -86,7 +87,9 @@ final class PhoneReverseExpander implements PhoneReverse if (strlen($digits) === self::PHONE_DIGITS && ! isset($seenPhones[$digits])) { $seenPhones[$digits] = true; $phones[] = ['digits' => $digits, 'url' => $firmUrl, 'source' => '2ГИС']; - $queue[] = $digits; // цепочка: по новому номеру тоже развернёмся + if ($depth < $this->maxDepth) { + $queue[] = [$digits, $depth + 1]; // цепочка «до талого» — только пока не упёрлись в глубину + } } } } diff --git a/app/app/Services/Autopodbor/Agent/Study/YandexPhoneReverseExpander.php b/app/app/Services/Autopodbor/Agent/Study/YandexPhoneReverseExpander.php index 27c35659..e576a6d4 100644 --- a/app/app/Services/Autopodbor/Agent/Study/YandexPhoneReverseExpander.php +++ b/app/app/Services/Autopodbor/Agent/Study/YandexPhoneReverseExpander.php @@ -22,6 +22,7 @@ final class YandexPhoneReverseExpander implements PhoneReverse private AutopodborNormalizer $norm = new AutopodborNormalizer, private int $crowdedCap = 4, private int $maxCards = 40, + private int $maxDepth = 1, ) {} public function expand(string $gisSlug, array $seedPhones, array $seedDomains, string $yandexSlug = ''): PhoneExpansionResult @@ -41,7 +42,7 @@ final class YandexPhoneReverseExpander implements PhoneReverse $d = $this->norm->phone((string) $p); if (strlen($d) === 11 && ! isset($seenPhones[$d])) { $seenPhones[$d] = true; - $queue[] = $d; + $queue[] = [$d, 1]; } } @@ -51,7 +52,8 @@ final class YandexPhoneReverseExpander implements PhoneReverse $phones = []; while ($queue !== [] && $cardsRead < $this->maxCards) { - $phone = (string) array_shift($queue); + [$phone, $depth] = array_shift($queue); + $phone = (string) $phone; $confirmed = []; foreach ($this->search->candidates($yandexSlug, $phone) as $cand) { @@ -98,7 +100,9 @@ final class YandexPhoneReverseExpander implements PhoneReverse if (! isset($seenPhones[$cp])) { $seenPhones[$cp] = true; $phones[] = ['digits' => $cp, 'url' => $c['url'], 'source' => 'Яндекс']; - $queue[] = $cp; + if ($depth < $this->maxDepth) { + $queue[] = [$cp, $depth + 1]; // цепочка «до талого» — только пока не упёрлись в глубину + } } } } diff --git a/app/tests/Unit/Autopodbor/Study/PhoneReverseExpanderTest.php b/app/tests/Unit/Autopodbor/Study/PhoneReverseExpanderTest.php index ea493bfb..25a338b0 100644 --- a/app/tests/Unit/Autopodbor/Study/PhoneReverseExpanderTest.php +++ b/app/tests/Unit/Autopodbor/Study/PhoneReverseExpanderTest.php @@ -61,7 +61,29 @@ it('пустой слаг города — разворот невозможен expect($res->sites)->toBe([])->and($res->phones)->toBe([]); }); -it('копает до талого: телефон A → карточка с телефоном B → карточка B даёт сайт проекта', function () { +it('копает до талого при maxDepth>=2: телефон A → карточка с телефоном B → карточка B даёт сайт проекта', function () { + $searchA = 'https://2gis.ru/krasnoyarsk/search/73912273531'; + $firm1 = 'https://2gis.ru/krasnoyarsk/firm/111'; + $searchB = 'https://2gis.ru/krasnoyarsk/search/73912002288'; + $firm2 = 'https://2gis.ru/krasnoyarsk/firm/222'; + + $fetch = fakeFetcher([ + $searchA => 'Управляющая компания', + $firm1 => '{"type":"phone","value":"+73912002288"}', + $searchB => 'Аллея парк, жилой комплекс', + $firm2 => '{"url":"https://alleya-park.ru","type":"website"}{"type":"phone","value":"+73912002288"}', + ]); + + $exp = new PhoneReverseExpander($fetch, maxDepth: 2); + $res = $exp->expand('krasnoyarsk', ['73912273531'], []); + + expect(array_column($res->sites, 'domain'))->toContain('alleya-park.ru'); +}); + +it('по умолчанию (глубина 1) НЕ уходит в цепочку — только фирмы с ЛИЧНЫМ номером, дрейф отсечён', function () { + // Те же данные, что и в тесте «до талого», но БЕЗ maxDepth (по умолчанию 1). + // firm/222 (Аллея парк) достижим только по номеру, найденному на firm/111 — цепочкой. + // При глубине 1 мы туда не идём: это и есть защита от дрейфа в чужие фирмы. $searchA = 'https://2gis.ru/krasnoyarsk/search/73912273531'; $firm1 = 'https://2gis.ru/krasnoyarsk/firm/111'; $searchB = 'https://2gis.ru/krasnoyarsk/search/73912002288'; @@ -77,7 +99,7 @@ it('копает до талого: телефон A → карточка с т $exp = new PhoneReverseExpander($fetch); $res = $exp->expand('krasnoyarsk', ['73912273531'], []); - expect(array_column($res->sites, 'domain'))->toContain('alleya-park.ru'); + expect(array_column($res->sites, 'domain'))->not->toContain('alleya-park.ru'); }); it('одну и ту же карточку не открывает дважды (visited-set)', function () { diff --git a/app/tests/Unit/Autopodbor/Study/YandexPhoneReverseExpanderTest.php b/app/tests/Unit/Autopodbor/Study/YandexPhoneReverseExpanderTest.php index 9ab9eb07..6271d62f 100644 --- a/app/tests/Unit/Autopodbor/Study/YandexPhoneReverseExpanderTest.php +++ b/app/tests/Unit/Autopodbor/Study/YandexPhoneReverseExpanderTest.php @@ -78,7 +78,21 @@ it('уже известный домен фирмы не повторяет', fu expect(array_column($res->sites, 'domain'))->not->toContain('sm-city.ru'); }); -it('цепочка: телефон найденной карточки разворачивается дальше', function () { +it('цепочка при maxDepth>=2: телефон найденной карточки разворачивается дальше', function () { + $search = fakeSearch([ + '73912179941' => [['name' => 'A', 'card_url' => 'https://yandex.ru/maps/org/a/1/']], + '73912000002' => [['name' => 'B', 'card_url' => 'https://yandex.ru/maps/org/b/2/']], + ]); + $cards = fakeCards([ + 'https://yandex.ru/maps/org/a/1/' => ['phones' => ['73912179941', '73912000002'], 'site' => 'https://a.ru'], + 'https://yandex.ru/maps/org/b/2/' => ['phones' => ['73912000002'], 'site' => 'https://b-proekt.ru'], + ]); + $exp = new YandexPhoneReverseExpander($search, $cards, maxDepth: 2); + $res = $exp->expand('', ['73912179941'], [], '62/krasnoyarsk'); + expect(array_column($res->sites, 'domain'))->toContain('b-proekt.ru'); +}); + +it('по умолчанию (глубина 1) НЕ разворачивает по номеру найденной карточки — дрейф отсечён', function () { $search = fakeSearch([ '73912179941' => [['name' => 'A', 'card_url' => 'https://yandex.ru/maps/org/a/1/']], '73912000002' => [['name' => 'B', 'card_url' => 'https://yandex.ru/maps/org/b/2/']], @@ -89,5 +103,5 @@ it('цепочка: телефон найденной карточки разв ]); $exp = new YandexPhoneReverseExpander($search, $cards); $res = $exp->expand('', ['73912179941'], [], '62/krasnoyarsk'); - expect(array_column($res->sites, 'domain'))->toContain('b-proekt.ru'); + expect(array_column($res->sites, 'domain'))->not->toContain('b-proekt.ru'); });