fix p47
This commit is contained in:
@@ -146,6 +146,7 @@ parameters:
|
|||||||
price_removal_minmax: '/\b(?:unter|bis|max(?:imal)?|ab|mindestens|min)\s+\d+(?:[.,]\d+)?\s*euro\b/u'
|
price_removal_minmax: '/\b(?:unter|bis|max(?:imal)?|ab|mindestens|min)\s+\d+(?:[.,]\d+)?\s*euro\b/u'
|
||||||
price_removal_intent_template: '/\b(?:{price_pattern})\b/u'
|
price_removal_intent_template: '/\b(?:{price_pattern})\b/u'
|
||||||
direct_product_digit: '/\d/u'
|
direct_product_digit: '/\d/u'
|
||||||
|
exact_product_number_search_text: '/^\s*(\d{4,12})\s*$/u'
|
||||||
model_like: '/\b[a-zäöüß][a-zäöüß®\-]*(?:\s+[a-zäöüß][a-zäöüß®\-]*){0,2}\s+\d{2,5}[a-z0-9\-]*\b/u'
|
model_like: '/\b[a-zäöüß][a-zäöüß®\-]*(?:\s+[a-zäöüß][a-zäöüß®\-]*){0,2}\s+\d{2,5}[a-z0-9\-]*\b/u'
|
||||||
accessory_like: '/\b(?:indikator|indicator|reagenz|reagent|kit|set)\s+\d{1,5}[a-z0-9\-]*\b/u'
|
accessory_like: '/\b(?:indikator|indicator|reagenz|reagent|kit|set)\s+\d{1,5}[a-z0-9\-]*\b/u'
|
||||||
contains_digit: '/\d/u'
|
contains_digit: '/\d/u'
|
||||||
|
|||||||
@@ -22,6 +22,23 @@ final readonly class CommerceQueryParser
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function extractExactProductNumberSearchText(string $searchText): ?string
|
||||||
|
{
|
||||||
|
$searchText = trim($this->normalize($searchText));
|
||||||
|
|
||||||
|
if ($searchText === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match($this->config->getExactProductNumberSearchTextPattern(), $searchText, $matches) !== 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$productNumber = trim((string) ($matches[1] ?? ''));
|
||||||
|
|
||||||
|
return $productNumber !== '' ? $productNumber : null;
|
||||||
|
}
|
||||||
|
|
||||||
public function parse(
|
public function parse(
|
||||||
string $originalPrompt,
|
string $originalPrompt,
|
||||||
string $intent,
|
string $intent,
|
||||||
|
|||||||
@@ -377,6 +377,18 @@ final class ShopSearchService
|
|||||||
string $originalPrompt,
|
string $originalPrompt,
|
||||||
bool $usesHistoryContext
|
bool $usesHistoryContext
|
||||||
): array {
|
): array {
|
||||||
|
$exactProductNumber = $this->queryParser->extractExactProductNumberSearchText($query->searchText);
|
||||||
|
|
||||||
|
if ($exactProductNumber !== null) {
|
||||||
|
return $this->executeExactProductNumberSearch(
|
||||||
|
productNumber: $exactProductNumber,
|
||||||
|
query: $query,
|
||||||
|
commerceIntent: $commerceIntent,
|
||||||
|
originalPrompt: $originalPrompt,
|
||||||
|
usesHistoryContext: $usesHistoryContext
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$criteria = $this->criteriaBuilder->build($query, $this->maxResults);
|
$criteria = $this->criteriaBuilder->build($query, $this->maxResults);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -448,6 +460,53 @@ final class ShopSearchService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ShopProductResult[]
|
||||||
|
*/
|
||||||
|
private function executeExactProductNumberSearch(
|
||||||
|
string $productNumber,
|
||||||
|
CommerceSearchQuery $query,
|
||||||
|
string $commerceIntent,
|
||||||
|
string $originalPrompt,
|
||||||
|
bool $usesHistoryContext
|
||||||
|
): array {
|
||||||
|
$criteria = $this->criteriaBuilder->buildExactProductNumber($productNumber, 5);
|
||||||
|
|
||||||
|
$this->logger->info('Shop search using exact product-number criteria', [
|
||||||
|
'commerceIntent' => $commerceIntent,
|
||||||
|
'originalPrompt' => $originalPrompt,
|
||||||
|
'normalizedPrompt' => $query->normalizedPrompt,
|
||||||
|
'searchText' => $query->searchText,
|
||||||
|
'productNumber' => $productNumber,
|
||||||
|
'usesHistoryContext' => $usesHistoryContext,
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = $this->storeApiClient->searchProducts($criteria);
|
||||||
|
|
||||||
|
return $this->mapAndLogSearchResponse(
|
||||||
|
response: $response,
|
||||||
|
query: $query,
|
||||||
|
commerceIntent: $commerceIntent,
|
||||||
|
originalPrompt: $originalPrompt,
|
||||||
|
usesHistoryContext: $usesHistoryContext,
|
||||||
|
usedSafeCriteria: true
|
||||||
|
);
|
||||||
|
} catch (
|
||||||
|
ClientExceptionInterface |
|
||||||
|
RedirectionExceptionInterface |
|
||||||
|
ServerExceptionInterface |
|
||||||
|
TransportExceptionInterface |
|
||||||
|
StoreApiException |
|
||||||
|
\RuntimeException $e
|
||||||
|
) {
|
||||||
|
$this->recordFailedSearch($e);
|
||||||
|
$this->logShopSearchFailure($query, $commerceIntent, $originalPrompt, $usesHistoryContext, $e);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return ShopProductResult[]|null
|
* @return ShopProductResult[]|null
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -206,6 +206,11 @@ final class CommerceQueryParserConfig
|
|||||||
return $this->string('patterns.direct_product_digit');
|
return $this->string('patterns.direct_product_digit');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getExactProductNumberSearchTextPattern(): string
|
||||||
|
{
|
||||||
|
return $this->string('patterns.exact_product_number_search_text');
|
||||||
|
}
|
||||||
|
|
||||||
public function getDirectProductMaxTokens(): int
|
public function getDirectProductMaxTokens(): int
|
||||||
{
|
{
|
||||||
return $this->int('limits.direct_product_max_tokens');
|
return $this->int('limits.direct_product_max_tokens');
|
||||||
|
|||||||
@@ -65,6 +65,66 @@ final class ShopwareCriteriaBuilder
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a narrow, rich-text-free criteria payload for exact article-number lookups.
|
||||||
|
* Pure product-number searches should not run through Shopware full-text search,
|
||||||
|
* because broad numeric search can surface unrelated products and trigger JSON
|
||||||
|
* encoding problems in rich text/custom fields.
|
||||||
|
*/
|
||||||
|
public function buildExactProductNumber(string $productNumber, ?int $limit = 5): array
|
||||||
|
{
|
||||||
|
$productNumber = trim($productNumber);
|
||||||
|
|
||||||
|
$criteria = [
|
||||||
|
'page' => 1,
|
||||||
|
'limit' => max(1, $limit),
|
||||||
|
'total-count-mode' => 0,
|
||||||
|
'includes' => [
|
||||||
|
'product' => [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'translated.name',
|
||||||
|
'productNumber',
|
||||||
|
'available',
|
||||||
|
'calculatedPrice',
|
||||||
|
'manufacturer',
|
||||||
|
],
|
||||||
|
'product_manufacturer' => [
|
||||||
|
'name',
|
||||||
|
],
|
||||||
|
'calculated_price' => [
|
||||||
|
'unitPrice',
|
||||||
|
'totalPrice',
|
||||||
|
'referencePrice',
|
||||||
|
'listPrice',
|
||||||
|
'regulationPrice',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'associations' => [
|
||||||
|
'manufacturer' => new \stdClass(),
|
||||||
|
],
|
||||||
|
'filter' => [
|
||||||
|
[
|
||||||
|
'type' => 'equals',
|
||||||
|
'field' => 'active',
|
||||||
|
'value' => true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'type' => 'equals',
|
||||||
|
'field' => 'available',
|
||||||
|
'value' => true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'type' => 'equals',
|
||||||
|
'field' => 'productNumber',
|
||||||
|
'value' => $productNumber,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
return $criteria;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds an ultra-safe Store API payload for the final recovery attempt.
|
* Builds an ultra-safe Store API payload for the final recovery attempt.
|
||||||
* It keeps only identity, availability and price fields and intentionally
|
* It keeps only identity, availability and price fields and intentionally
|
||||||
|
|||||||
Reference in New Issue
Block a user