428b7b0f01
Клиент во время сбора видит место в общей очереди (пока ждёт) и текущий этап (пока идёт). Очередь — длина общей Redis-очереди (честное ожидание, без RLS, одинаково dev/прод). Этапы пишет сам движок через RunProgressChannel: поиск (анализ→справочники→федералы→сбор→отбор), изучение — по каждому элементу. - миграция progress (jsonb) на autopodbor_runs + cast - RunResource отдаёт queue_position (AutopodborQueue) и progress - RunProgressChannel (синглтон), джобы привязывают его к своему прогону - SearchStages + инструментовка LiveFindCompetitors и RealCompetitorAgent - 16 тестов (Feature+Unit), все зелёные Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
98 lines
3.9 KiB
PHP
98 lines
3.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Services\Autopodbor\Agent\Aggregator\AggregatorFilter;
|
|
use App\Services\Autopodbor\Agent\Aggregator\AitunnelAggregatorClassifier;
|
|
use App\Services\Autopodbor\Agent\ChannelA\CategoryListingParser;
|
|
use App\Services\Autopodbor\Agent\ChannelA\CategoryScraper;
|
|
use App\Services\Autopodbor\Agent\ChannelA\QueryAnalyzer;
|
|
use App\Services\Autopodbor\Agent\ChannelA\YandexDirectory;
|
|
use App\Services\Autopodbor\Agent\ChannelB\AitunnelResearcher;
|
|
use App\Services\Autopodbor\Agent\ChannelB\ChannelBSearch;
|
|
use App\Services\Autopodbor\Agent\ChannelB\ExaSiteFinder;
|
|
use App\Services\Autopodbor\Agent\ChannelB\ResearcherParser;
|
|
use App\Services\Autopodbor\Agent\Dto\FindCompetitorsRequest;
|
|
use App\Services\Autopodbor\Agent\Fetch\LivePageFetcher;
|
|
use App\Services\Autopodbor\Agent\Fetch\XfetchClient;
|
|
use App\Services\Autopodbor\Agent\FindCompetitorsAssembler;
|
|
use App\Services\Autopodbor\Agent\LiveFindCompetitors;
|
|
use App\Services\Autopodbor\Agent\Similarity\AitunnelEmbedder;
|
|
use App\Services\Autopodbor\Agent\Similarity\EmbeddingRelevance;
|
|
use App\Services\Autopodbor\AutopodborDedup;
|
|
use App\Services\Autopodbor\AutopodborNormalizer;
|
|
use App\Services\Autopodbor\RunProgressChannel;
|
|
use Illuminate\Http\Client\Factory as HttpFactory;
|
|
|
|
/** Канал-шпион: копит вызовы этапов вместо записи в БД. */
|
|
function spyProgressChannel(): RunProgressChannel
|
|
{
|
|
return new class extends RunProgressChannel
|
|
{
|
|
/** @var list<array{int,int,string}> */
|
|
public array $calls = [];
|
|
|
|
public function stage(int $stage, int $total, string $label): void
|
|
{
|
|
$this->calls[] = [$stage, $total, $label];
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Живой движок с заглушками-интерфейсами (анализ отдаёт пустые запросы → живые каналы не дёргаются) и
|
|
* дешёвыми экземплярами конкретных зависимостей (они не вызываются при пустом наборе). Так проверяем
|
|
* ТОЛЬКО последовательность этапов, не поднимая сеть.
|
|
*/
|
|
function liveFindWithProgress(RunProgressChannel $progress, bool $withEnricher = false): LiveFindCompetitors
|
|
{
|
|
$http = app(HttpFactory::class);
|
|
$xfetch = new XfetchClient(apiKey: '', endpoint: 'https://xf4.ru/fetch', concurrency: 1);
|
|
|
|
$analyzer = new class implements QueryAnalyzer
|
|
{
|
|
public function analyze(string $description, string $region, string $selfSite = '', array $competitorExamples = []): array
|
|
{
|
|
return [];
|
|
}
|
|
};
|
|
$yandex = new class implements YandexDirectory
|
|
{
|
|
public function collect(string $city, array $queries): array
|
|
{
|
|
return [];
|
|
}
|
|
};
|
|
|
|
$assembler = new FindCompetitorsAssembler(
|
|
new AggregatorFilter(app(AitunnelAggregatorClassifier::class)),
|
|
new AutopodborDedup(new AutopodborNormalizer),
|
|
new EmbeddingRelevance(app(AitunnelEmbedder::class)),
|
|
);
|
|
|
|
return new LiveFindCompetitors(
|
|
$analyzer,
|
|
new CategoryScraper(new LivePageFetcher($xfetch), new CategoryListingParser, 4),
|
|
$yandex,
|
|
new ChannelBSearch(new AitunnelResearcher($http), new ResearcherParser),
|
|
new ExaSiteFinder($http),
|
|
$assembler,
|
|
progress: $progress,
|
|
);
|
|
}
|
|
|
|
it('поиск отмечает этапы в канал по порядку (базовый: 3 этапа)', function () {
|
|
$spy = spyProgressChannel();
|
|
$live = liveFindWithProgress($spy);
|
|
|
|
$live->find(new FindCompetitorsRequest(
|
|
regionCode: 16, examples: [], aboutSelf: [], includeFederal: false, maxCompetitors: 15,
|
|
));
|
|
|
|
expect($spy->calls)->toBe([
|
|
[1, 3, 'Разбираем ваш запрос'],
|
|
[2, 3, 'Ищем фирмы в справочниках'],
|
|
[3, 3, 'Отбираем самых похожих'],
|
|
]);
|
|
});
|