optimize technical truth
This commit is contained in:
@@ -175,6 +175,12 @@ final class ShopSearchService
|
||||
referenceContext: $referenceContext
|
||||
);
|
||||
|
||||
$finalProducts = $this->applyRoleGuardrails(
|
||||
products: $finalProducts,
|
||||
query: $primaryQuery,
|
||||
originalPrompt: $originalPrompt
|
||||
);
|
||||
|
||||
$finalProducts = $this->applyPriceFilters(
|
||||
products: $finalProducts,
|
||||
query: $primaryQuery
|
||||
@@ -984,6 +990,69 @@ final class ShopSearchService
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ShopProductResult[] $products
|
||||
* @return ShopProductResult[]
|
||||
*/
|
||||
private function applyRoleGuardrails(array $products, CommerceSearchQuery $query, string $originalPrompt): array
|
||||
{
|
||||
if ($products === [] || !$this->shopConfig->shouldFilterAccessoryProductsForDeviceQueries()) {
|
||||
return $products;
|
||||
}
|
||||
|
||||
$normalizedQuery = $this->normalizeForMatching(trim(implode(' ', array_filter([
|
||||
$query->normalizedPrompt !== '' ? $query->normalizedPrompt : $query->originalPrompt,
|
||||
$query->searchText,
|
||||
$originalPrompt,
|
||||
]))));
|
||||
|
||||
if (!$this->isDeviceQuery($normalizedQuery) || $this->isAccessoryQuery($normalizedQuery)) {
|
||||
return $products;
|
||||
}
|
||||
|
||||
$filtered = [];
|
||||
$excluded = [];
|
||||
|
||||
foreach ($products as $product) {
|
||||
if (!$product instanceof ShopProductResult) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isAccessoryLike = $this->isAccessoryLikeProduct($product);
|
||||
$isDeviceLike = $this->isDeviceLikeProduct($product);
|
||||
|
||||
if ($isAccessoryLike && !$isDeviceLike) {
|
||||
$excluded[] = $product;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$isDeviceLike && !$this->shopConfig->shouldKeepAmbiguousProductsForDeviceQueries()) {
|
||||
$excluded[] = $product;
|
||||
continue;
|
||||
}
|
||||
|
||||
$filtered[] = $product;
|
||||
}
|
||||
|
||||
if ($excluded !== []) {
|
||||
$this->logger->info('Shop role guard excluded accessory-like products for device query', [
|
||||
'originalPrompt' => $originalPrompt,
|
||||
'searchText' => $query->searchText,
|
||||
'excludedCount' => count($excluded),
|
||||
'keptCount' => count($filtered),
|
||||
'excludedProducts' => array_map(
|
||||
static fn(ShopProductResult $product): array => [
|
||||
'name' => $product->name,
|
||||
'productNumber' => $product->productNumber,
|
||||
],
|
||||
array_slice($excluded, 0, $this->shopConfig->getTopProductLogLimit())
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
return array_values($filtered);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $focusTerms
|
||||
*/
|
||||
@@ -1261,23 +1330,89 @@ final class ShopSearchService
|
||||
|
||||
private function isAccessoryLikeProduct(ShopProductResult $product): bool
|
||||
{
|
||||
$corpus = $this->buildNormalizedProductCorpus($product);
|
||||
$primaryRole = $this->resolvePrimaryShopProductRole($product);
|
||||
|
||||
foreach ($this->shopConfig->getAccessoryProductKeywords() as $keyword) {
|
||||
if (str_contains($corpus, $this->normalizeForMatching($keyword))) {
|
||||
return true;
|
||||
}
|
||||
if ($primaryRole === 'accessory_or_consumable') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
if ($primaryRole === 'main_device') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->containsAnyShopKeyword(
|
||||
$this->buildNormalizedProductCorpus($product),
|
||||
$this->shopConfig->getAccessoryProductKeywords()
|
||||
);
|
||||
}
|
||||
|
||||
private function isDeviceLikeProduct(ShopProductResult $product): bool
|
||||
{
|
||||
$corpus = $this->buildNormalizedProductCorpus($product);
|
||||
$primaryRole = $this->resolvePrimaryShopProductRole($product);
|
||||
|
||||
foreach ($this->shopConfig->getDeviceProductKeywords() as $keyword) {
|
||||
if (str_contains($corpus, $this->normalizeForMatching($keyword))) {
|
||||
if ($primaryRole === 'main_device') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($primaryRole === 'accessory_or_consumable') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->containsAnyShopKeyword(
|
||||
$this->buildNormalizedProductCorpus($product),
|
||||
$this->shopConfig->getDeviceProductKeywords()
|
||||
);
|
||||
}
|
||||
|
||||
private function resolvePrimaryShopProductRole(ShopProductResult $product): string
|
||||
{
|
||||
$primaryText = $this->buildNormalizedPrimaryProductIdentity($product);
|
||||
|
||||
if ($primaryText === '') {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
$isAccessoryLike = $this->containsAnyShopKeyword(
|
||||
$primaryText,
|
||||
$this->shopConfig->getAccessoryProductKeywords()
|
||||
);
|
||||
$isDeviceLike = $this->containsAnyShopKeyword(
|
||||
$primaryText,
|
||||
$this->shopConfig->getDeviceProductKeywords()
|
||||
);
|
||||
|
||||
if ($isAccessoryLike && !$isDeviceLike) {
|
||||
return 'accessory_or_consumable';
|
||||
}
|
||||
|
||||
if ($isDeviceLike && !$isAccessoryLike) {
|
||||
return 'main_device';
|
||||
}
|
||||
|
||||
if ($isAccessoryLike && $isDeviceLike) {
|
||||
return 'ambiguous_mixed_role';
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
private function buildNormalizedPrimaryProductIdentity(ShopProductResult $product): string
|
||||
{
|
||||
return $this->normalizeForMatching(implode(' ', array_filter([
|
||||
$product->name,
|
||||
$product->url,
|
||||
])));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $keywords
|
||||
*/
|
||||
private function containsAnyShopKeyword(string $normalizedText, array $keywords): bool
|
||||
{
|
||||
foreach ($keywords as $keyword) {
|
||||
$normalizedKeyword = $this->normalizeForMatching((string) $keyword);
|
||||
|
||||
if ($normalizedKeyword !== '' && str_contains($normalizedText, $normalizedKeyword)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user