diff --git a/src/Commerce/CommerceQueryParser.php b/src/Commerce/CommerceQueryParser.php index 6a113e4..87bf752 100644 --- a/src/Commerce/CommerceQueryParser.php +++ b/src/Commerce/CommerceQueryParser.php @@ -131,13 +131,13 @@ final readonly class CommerceQueryParser return []; } - foreach ($matches[1] as $size) { - $sizes[] = trim($size); + foreach ($matches[1] ?? [] as $size) { + $sizes[] = trim((string) $size); } if (preg_match_all($this->intentConfig->getSizeTokenValuePattern(), $prompt, $tokenMatches) !== false) { - foreach ($tokenMatches[1] as $sizeToken) { - $sizes[] = trim($sizeToken); + foreach ($tokenMatches[0] ?? [] as $sizeToken) { + $sizes[] = trim((string) $sizeToken); } } @@ -242,6 +242,7 @@ final readonly class CommerceQueryParser fn(string $token): bool => mb_strlen($token) >= $this->config->getMinDirectProductTokenLength() ); + $tokens = $this->filterSearchTokens($tokens); $tokens = array_values(array_unique($tokens)); return trim(implode(' ', $tokens)); @@ -286,6 +287,10 @@ final readonly class CommerceQueryParser continue; } + if ($this->isSearchControlToken($token)) { + continue; + } + $tokens[$token] = $token; } } @@ -299,14 +304,38 @@ final readonly class CommerceQueryParser */ private function filterSearchTokens(array $tokens): array { - $stopWords = $this->config->getFilterSearchTokens(); - return array_values(array_filter( $tokens, - static fn(string $token): bool => !in_array($token, $stopWords, true) + fn(string $token): bool => !$this->isSearchControlToken($token) )); } + private function isSearchControlToken(string $token): bool + { + $token = trim(mb_strtolower($token)); + + if ($token === '') { + return true; + } + + if (in_array($token, $this->config->getFilterSearchTokens(), true)) { + return true; + } + + return in_array($token, [ + 'shop', + 'store', + 'produkt', + 'produkte', + 'artikel', + 'kaufen', + 'kaufe', + 'bestellen', + 'bestelle', + 'online', + ], true); + } + private function isDirectProductQuery(string $prompt): bool { if ($prompt === '') { @@ -328,8 +357,10 @@ final readonly class CommerceQueryParser PREG_SPLIT_NO_EMPTY ) ?: []; + $tokens = $this->filterSearchTokens($tokens); + return count($tokens) <= $this->config->getDirectProductMaxTokens() - && preg_match($this->config->getDirectProductDigitPattern(), $prompt) === 1; + && preg_match($this->config->getDirectProductDigitPattern(), implode(' ', $tokens)) === 1; } private function containsModelLikePhrase(string $text): bool diff --git a/src/Config/CommerceIntentConfig.php b/src/Config/CommerceIntentConfig.php index 04d8e40..2cb278c 100644 --- a/src/Config/CommerceIntentConfig.php +++ b/src/Config/CommerceIntentConfig.php @@ -296,4 +296,19 @@ final class CommerceIntentConfig { return 1; } + + public function getModelLikeProductPattern(): string + { + return '/\b[a-zäöüß][a-zäöüß®\-]*(?:\s+[a-zäöüß][a-zäöüß®\-]*){0,2}\s+\d{2,5}[a-z0-9\-]*\b/u'; + } + + public function getModelLikeProductSignalLabel(): string + { + return 'model_like_product'; + } + + public function getModelLikeProductSignalScore(): int + { + return 3; + } } \ No newline at end of file diff --git a/src/Intent/CommerceIntentLite.php b/src/Intent/CommerceIntentLite.php index de581f0..ce232c2 100644 --- a/src/Intent/CommerceIntentLite.php +++ b/src/Intent/CommerceIntentLite.php @@ -45,6 +45,7 @@ final class CommerceIntentLite [$score, $signals] = $this->applyStrongSignals($prompt, $score, $signals); [$score, $signals] = $this->applySkuSignal($prompt, $score, $signals); + [$score, $signals] = $this->applyModelLikeProductSignal($prompt, $score, $signals); [$score, $signals] = $this->applyPriceSignal($prompt, $score, $signals); [$score, $signals] = $this->applySizeSignal($prompt, $score, $signals); [$score, $signals] = $this->applySizeTokenSignal($prompt, $score, $signals); @@ -144,6 +145,16 @@ final class CommerceIntentLite return [$score, $signals]; } + private function applyModelLikeProductSignal(string $prompt, int $score, array $signals): array + { + if (preg_match($this->config->getModelLikeProductPattern(), $prompt) === 1) { + $score += $this->config->getModelLikeProductSignalScore(); + $signals[] = $this->config->getModelLikeProductSignalLabel(); + } + + return [$score, $signals]; + } + /** * @param string[] $signals * @return array{0:int,1:string[]}