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');
});