Files
portal/app/app/Services/Autopodbor/ProposalClassifier.php
T
Дмитрий 6d48503a0e feat(автоподбор): сверка находок со состоянием клиента — 3 группы предложений + скрытые дубли, мягкое удаление в архив, убран вычет себя
Stage 1: опознавалка фирмы по сайту/карточкам, ProposalClassifier new/actualize/archived/hidden, box=archived, bulk удалить всех ранее удалённых. Backend 249/249, front autopodbor 61/61.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 15:45:11 +03:00

73 lines
3.1 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\Autopodbor;
/**
* Делит находки-предложения относительно состояния клиента (поле/предложения/архив) на группы
* (спека §3): new / actualize / archived / hidden. «Та же фирма» — по пересечению опознавалок
* {@see CompetitorIdentity} (сайт+карточки, имя не участвует).
*/
final class ProposalClassifier
{
public function __construct(private readonly CompetitorIdentity $identity) {}
/**
* @param array<int, array{id:int,name?:string,site_url?:?string,directory_urls?:array}> $finds
* @param array<int, array{id:int,box:string,name?:string,site_url?:?string,directory_urls?:array}> $existing
* @return list<array{find:array,group:string,matched_id:?int,delta_keys:list<string>}>
*/
public function classify(array $finds, array $existing): array
{
// Индекс: ключ-опознавалка → список [existing_id, активна ли] (для приоритета активного).
$index = [];
$keysById = [];
foreach ($existing as $e) {
$keys = $this->identity->keys($e);
$keysById[$e['id']] = $keys;
$active = $e['box'] !== 'archived';
foreach ($keys as $k) {
$index[$k][] = ['id' => $e['id'], 'active' => $active];
}
}
$out = [];
foreach ($finds as $find) {
$findKeys = $this->identity->keys($find);
// Первое активное и первое архивное совпадение по общим ключам.
$matchActive = null;
$matchArchived = null;
foreach ($findKeys as $k) {
foreach ($index[$k] ?? [] as $hit) {
if ($hit['active'] && $matchActive === null) {
$matchActive = $hit['id'];
}
if (! $hit['active'] && $matchArchived === null) {
$matchArchived = $hit['id'];
}
}
}
if ($matchActive !== null) {
// Дельта = ключи находки, которых нет у совпавшей активной фирмы.
$have = array_flip($keysById[$matchActive] ?? []);
$delta = array_values(array_filter($findKeys, static fn (string $k): bool => ! isset($have[$k])));
$out[] = [
'find' => $find,
'group' => $delta === [] ? 'hidden' : 'actualize',
'matched_id' => $matchActive,
'delta_keys' => $delta,
];
} elseif ($matchArchived !== null) {
$out[] = ['find' => $find, 'group' => 'archived', 'matched_id' => $matchArchived, 'delta_keys' => []];
} else {
$out[] = ['find' => $find, 'group' => 'new', 'matched_id' => null, 'delta_keys' => []];
}
}
return $out;
}
}