optimize technical truth

This commit is contained in:
team 1
2026-04-28 17:00:12 +02:00
parent 8d9f863143
commit bca015129c
5 changed files with 471 additions and 314 deletions

View File

@@ -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;
}
}