119 lines
3.9 KiB
PHP
119 lines
3.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Dto;
|
|
|
|
use App\Models\SupplierLead;
|
|
|
|
/**
|
|
* Результат резолва региона лида (LeadRegionResolver, spec §3.3).
|
|
*
|
|
* `subjectCode` — итоговый код субъекта (используется маршрутизатором);
|
|
* `actualSubjectCode` — настоящий резолв (для лога actual_subject_code; на этапе
|
|
* резолва равен subjectCode, подмена региона — концерн RouteSupplierLeadJob §3.10).
|
|
* `source` ∈ dadata|rossvyaz|tag|unknown — ранг см. SOURCE_RANK (CSV-merge §3.12).
|
|
*/
|
|
final readonly class RegionResolution
|
|
{
|
|
/** @var array<string, int> ранг источника для CSV-merge (выше = достовернее) */
|
|
public const SOURCE_RANK = [
|
|
'dadata' => 4,
|
|
'rossvyaz' => 3,
|
|
'tag' => 2,
|
|
'unknown' => 1,
|
|
];
|
|
|
|
/**
|
|
* @param array<string, mixed>|null $dadataResponseMasked
|
|
*/
|
|
public function __construct(
|
|
public ?int $subjectCode,
|
|
public ?int $actualSubjectCode,
|
|
public string $source,
|
|
public ?string $phoneOperator,
|
|
public ?int $qc,
|
|
public bool $cacheHit,
|
|
public ?array $dadataResponseMasked,
|
|
public ?int $durationMs,
|
|
public bool $rossvyazMatched,
|
|
) {}
|
|
|
|
/**
|
|
* @param array<string, mixed>|null $dadataMasked
|
|
*/
|
|
public static function make(
|
|
?int $subjectCode,
|
|
string $source,
|
|
?string $operator = null,
|
|
?int $qc = null,
|
|
bool $cacheHit = false,
|
|
?array $dadataMasked = null,
|
|
?int $durationMs = null,
|
|
bool $rossvyazMatched = false,
|
|
): self {
|
|
return new self(
|
|
subjectCode: $subjectCode,
|
|
actualSubjectCode: $subjectCode,
|
|
source: $source,
|
|
phoneOperator: $operator,
|
|
qc: $qc,
|
|
cacheHit: $cacheHit,
|
|
dadataResponseMasked: $dadataMasked,
|
|
durationMs: $durationMs,
|
|
rossvyazMatched: $rossvyazMatched,
|
|
);
|
|
}
|
|
|
|
public static function fromTag(?int $subjectCode): self
|
|
{
|
|
return self::make($subjectCode, $subjectCode !== null ? 'tag' : 'unknown');
|
|
}
|
|
|
|
/**
|
|
* Восстановление из persistent state лида (retry-идемпотентность §3.11) — без DaData-вызова.
|
|
*/
|
|
public static function fromSupplierLead(SupplierLead $lead): self
|
|
{
|
|
return self::make(
|
|
subjectCode: $lead->resolved_subject_code !== null ? (int) $lead->resolved_subject_code : null,
|
|
source: (string) ($lead->region_source ?? 'unknown'),
|
|
operator: $lead->phone_operator,
|
|
qc: $lead->dadata_qc !== null ? (int) $lead->dadata_qc : null,
|
|
);
|
|
}
|
|
|
|
public function withCacheHit(bool $hit): self
|
|
{
|
|
return new self(
|
|
subjectCode: $this->subjectCode,
|
|
actualSubjectCode: $this->actualSubjectCode,
|
|
source: $this->source,
|
|
phoneOperator: $this->phoneOperator,
|
|
qc: $this->qc,
|
|
cacheHit: $hit,
|
|
dadataResponseMasked: null, // §3.11: cache-hit лог не несёт masked-ответ
|
|
durationMs: $this->durationMs,
|
|
rossvyazMatched: $this->rossvyazMatched,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Версия для записи в кэш (§7.3): без per-call полей (masked-ответ, длительность, cache-флаг).
|
|
*/
|
|
public function forCache(): self
|
|
{
|
|
return new self(
|
|
subjectCode: $this->subjectCode,
|
|
actualSubjectCode: $this->actualSubjectCode,
|
|
source: $this->source,
|
|
phoneOperator: $this->phoneOperator,
|
|
qc: $this->qc,
|
|
cacheHit: false,
|
|
dadataResponseMasked: null,
|
|
durationMs: null,
|
|
rossvyazMatched: $this->rossvyazMatched,
|
|
);
|
|
}
|
|
}
|