From cbae23ba8693637fef78bd5c7f276c2bc2fbc337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Sun, 28 Jun 2026 13:28:56 +0300 Subject: [PATCH] =?UTF-8?q?feat(=D0=B0=D0=B2=D1=82=D0=BE=D0=BF=D0=BE=D0=B4?= =?UTF-8?q?=D0=B1=D0=BE=D1=80):=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D1=84?= =?UTF-8?q?=D0=B5=D0=B9=D1=81=20=D0=B4=D0=B2=D0=B8=D0=B6=D0=BA=D0=B0=20Com?= =?UTF-8?q?petitorAgent=20+=20=D0=B7=D0=B0=D0=B3=D0=BB=D1=83=D1=88=D0=BA?= =?UTF-8?q?=D0=B0=20+=20binding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CompetitorAgent interface: findCompetitors / studyCompetitor / resolveByName - FakeCompetitorAgent: 4 конкурента, 5 источников, 1 кандидат по имени - AutopodborServiceProvider: bind(CompetitorAgent → FakeCompetitorAgent) - Регистрация провайдера в bootstrap/providers.php (Laravel 11+) - Pest.php: extend TestCase для Unit/Autopodbor (контейнер в Unit-тестах) - Тест: 1/1 PASS, 10 assertions Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Providers/AutopodborServiceProvider.php | 16 +++++++++ .../Autopodbor/Agent/CompetitorAgent.php | 12 +++++++ .../Autopodbor/Agent/FakeCompetitorAgent.php | 36 +++++++++++++++++++ app/bootstrap/providers.php | 1 + app/tests/Pest.php | 4 +++ .../Autopodbor/FakeCompetitorAgentTest.php | 20 +++++++++++ 6 files changed, 89 insertions(+) create mode 100644 app/app/Providers/AutopodborServiceProvider.php create mode 100644 app/app/Services/Autopodbor/Agent/CompetitorAgent.php create mode 100644 app/app/Services/Autopodbor/Agent/FakeCompetitorAgent.php create mode 100644 app/tests/Unit/Autopodbor/FakeCompetitorAgentTest.php diff --git a/app/app/Providers/AutopodborServiceProvider.php b/app/app/Providers/AutopodborServiceProvider.php new file mode 100644 index 00000000..e8a347b1 --- /dev/null +++ b/app/app/Providers/AutopodborServiceProvider.php @@ -0,0 +1,16 @@ +app->bind(CompetitorAgent::class, FakeCompetitorAgent::class); + } +} diff --git a/app/app/Services/Autopodbor/Agent/CompetitorAgent.php b/app/app/Services/Autopodbor/Agent/CompetitorAgent.php new file mode 100644 index 00000000..79abf78b --- /dev/null +++ b/app/app/Services/Autopodbor/Agent/CompetitorAgent.php @@ -0,0 +1,12 @@ + 'Окна Комфорт', 'description' => 'Пластиковые окна и остекление балконов под ключ.', 'is_federal' => false, 'relevance_pct' => 100, 'site_url' => 'okna-komfort-kzn.ru', 'directory_urls' => ['https://2gis.ru/firm/1'], 'provenance' => ['via' => 'similar-pages']], + ['name' => 'Пластика Окон', 'description' => 'Окна ПВХ, лоджии, входные группы.', 'is_federal' => false, 'relevance_pct' => 96, 'site_url' => 'plastika-okon-kzn.ru', 'directory_urls' => ['https://2gis.ru/firm/2'], 'provenance' => ['via' => 'similar-pages']], + ['name' => 'Фабрика Окон', 'description' => 'Федеральная сеть окон ПВХ, филиал в регионе.', 'is_federal' => true, 'relevance_pct' => 84, 'site_url' => 'fabrika-okon.ru', 'directory_urls' => ['https://2gis.ru/firm/3'], 'provenance' => ['via' => 'similar-pages']], + ['name' => 'Балкон-Сервис 16', 'description' => 'Остекление балконов; окна частично.', 'is_federal' => false, 'relevance_pct' => 61, 'site_url' => null, 'directory_urls' => ['https://yandex.ru/maps/4', 'https://2gis.ru/firm/4'], 'provenance' => ['via' => 'similar-pages']], + ]); + } + + public function studyCompetitor(StudyCompetitorRequest $r): StudyCompetitorResult + { + return new StudyCompetitorResult([ + ['signal_type' => 'site', 'identifier' => 'okna-komfort-kzn.ru', 'phone_kind' => null, 'provenance_url' => 'https://2gis.ru/firm/1', 'provenance_label' => '2ГИС — карточка компании'], + ['signal_type' => 'site', 'identifier' => 'okna-komfort.pro', 'phone_kind' => null, 'provenance_url' => 'https://yandex.ru/maps/1', 'provenance_label' => 'Яндекс.Карты — сайт в контактах'], + ['signal_type' => 'call', 'identifier' => '78432001122', 'phone_kind' => 'real', 'provenance_url' => 'https://2gis.ru/firm/1', 'provenance_label' => '2ГИС — карточка компании'], + ['signal_type' => 'call', 'identifier' => '78432009988', 'phone_kind' => 'substitute', 'provenance_url' => 'https://okna-komfort-kzn.ru', 'provenance_label' => 'номер в шапке (коллтрекинг)'], + ['signal_type' => 'call', 'identifier' => '79172001122', 'phone_kind' => 'real', 'provenance_url' => 'https://yandex.ru/maps/1', 'provenance_label' => 'Яндекс.Карты — карточка компании'], + ]); + } + + public function resolveByName(ResolveByNameRequest $r): ResolveByNameResult + { + return new ResolveByNameResult([ + ['name' => $r->name, 'description' => 'Найдено по названию (заглушка).', 'site_url' => 'okna-komfort-kzn.ru', 'directory_urls' => ['https://2gis.ru/firm/1'], 'provenance' => ['via' => 'name-search']], + ]); + } +} diff --git a/app/bootstrap/providers.php b/app/bootstrap/providers.php index fc94ae60..cd0a01ce 100644 --- a/app/bootstrap/providers.php +++ b/app/bootstrap/providers.php @@ -4,4 +4,5 @@ use App\Providers\AppServiceProvider; return [ AppServiceProvider::class, + App\Providers\AutopodborServiceProvider::class, ]; diff --git a/app/tests/Pest.php b/app/tests/Pest.php index cdbb96b7..d70d5289 100644 --- a/app/tests/Pest.php +++ b/app/tests/Pest.php @@ -30,6 +30,10 @@ uses(SharesAdminPdo::class)->in('Feature'); pest()->extend(TestCase::class)->in('Browser'); +// Unit/Autopodbor: часть тестов использует app() и requires Laravel-контейнер +// (в т.ч. FakeCompetitorAgentTest — резолвит CompetitorAgent через провайдер). +pest()->extend(TestCase::class)->in('Unit/Autopodbor'); + /* |-------------------------------------------------------------------------- | Expectations diff --git a/app/tests/Unit/Autopodbor/FakeCompetitorAgentTest.php b/app/tests/Unit/Autopodbor/FakeCompetitorAgentTest.php new file mode 100644 index 00000000..c957cc9f --- /dev/null +++ b/app/tests/Unit/Autopodbor/FakeCompetitorAgentTest.php @@ -0,0 +1,20 @@ +toBeInstanceOf(\App\Services\Autopodbor\Agent\FakeCompetitorAgent::class); + + $found = $agent->findCompetitors(new FindCompetitorsRequest(16, ['okna.ru'], [], true, 15)); + expect(count($found->competitors))->toBeGreaterThan(0) + ->and($found->competitors[0])->toHaveKeys(['name','relevance_pct','site_url']); + + $study = $agent->studyCompetitor(new StudyCompetitorRequest(['name'=>'Окна Комфорт','site_url'=>'okna-komfort-kzn.ru'], 16)); + expect($study->sources)->not->toBeEmpty() + ->and($study->sources[0])->toHaveKeys(['signal_type','identifier','provenance_url']); + + $resolve = $agent->resolveByName(new ResolveByNameRequest('Окна Комфорт', 16)); + expect($resolve->candidates)->not->toBeEmpty(); +});