5a65165114
Замена вырожденного «одна фраза → одна страница» на §12/§11.3 финал: - Шаг АНАЛИЗ (ChannelA\AitunnelQueryAnalyzer): описание → запросы-рубрики (мелкая модель). - Канал А (ChannelA\CategoryScraper): скрейп категории 2ГИС с пагинацией → резолв карточек. - Канал В (ChannelB\*): ОДНА модель sonar-reasoning-pro × 2 прохода → ТОЛЬКО имена федералов; стоп-лист = имена из А + примеры; сайт федерала через EXA (ExaSiteFinder), т.к. у федерала нет карточки в 2ГИС/Яндексе на регион. - Оркестратор LiveFindCompetitors переписан: АНАЛИЗ→А→В→слияние→отсев→дедуп→похожесть→DTO. - Провайдер перепрошит; config services.php +research_model/exa. Похожесть — эмбеддер-модель (математически), резолвер/дедуп — без изменений. Всё за тонкими границами, офлайн-тесты на фикстурах: модуль 130 unit + 74 feature зелёные. Провайдер за флагом autopodbor.real_find; на проде не меняется. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
49 lines
2.1 KiB
PHP
49 lines
2.1 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
use App\Services\Autopodbor\Agent\ChannelB\ResearcherParser;
|
||
|
||
// Канал В, ФИНАЛ (ZAFIKSIROVANO §0-БИС + §11.3): ИИ даёт ТОЛЬКО НАЗВАНИЯ (+ тип), без сайтов/карточек.
|
||
// Якоря (сайт/2ГИС/Яндекс/телефоны) добывает ПОТОМ Firecrawl/резолвер. Парсер вытаскивает имена
|
||
// из сырого ответа модели (часто JSON в markdown-обёртке). Чистая логика, без сети.
|
||
|
||
beforeEach(function () {
|
||
$this->parser = new ResearcherParser;
|
||
});
|
||
|
||
it('разбирает чистый JSON в имена + тип', function () {
|
||
$raw = '[{"name":"CarMoney","type":"федеральная"},{"name":"Ваш инвестор","type":"региональная"}]';
|
||
|
||
$out = $this->parser->parse($raw);
|
||
|
||
expect($out)->toHaveCount(2)
|
||
->and($out[0]['name'])->toBe('CarMoney')
|
||
->and($out[0]['type'])->toBe('федеральная')
|
||
->and($out[1]['name'])->toBe('Ваш инвестор');
|
||
});
|
||
|
||
it('вытаскивает JSON из markdown-обёртки и текста вокруг', function () {
|
||
$raw = "Вот названия:\n```json\n".'[{"name":"Финео","type":"федеральная"}]'."\n```\nГотово.";
|
||
|
||
$out = $this->parser->parse($raw);
|
||
|
||
expect($out)->toHaveCount(1)->and($out[0]['name'])->toBe('Финео');
|
||
});
|
||
|
||
it('пропускает элемент без имени; тип может отсутствовать', function () {
|
||
$raw = '[{"name":"","type":"региональная"},{"name":"Голд Авто Инвест"}]';
|
||
|
||
$out = $this->parser->parse($raw);
|
||
|
||
expect($out)->toHaveCount(1)
|
||
->and($out[0]['name'])->toBe('Голд Авто Инвест')
|
||
->and($out[0]['type'])->toBeNull();
|
||
});
|
||
|
||
it('пустой массив и мусор дают []', function () {
|
||
expect($this->parser->parse('[]'))->toBe([])
|
||
->and($this->parser->parse('извините, ничего'))->toBe([])
|
||
->and($this->parser->parse(''))->toBe([]);
|
||
});
|