This commit is contained in:
team 1
2026-04-30 16:07:20 +02:00
parent 12577be636
commit b3b294bfc2
4 changed files with 187 additions and 188 deletions

View File

@@ -6,153 +6,6 @@ namespace App\Config;
final class CommerceIntentConfig
{
private const STRONG_SIGNALS = [
'shop',
'alle',
'preis',
'kunde',
'online',
'produkt',
'artikel',
'sku',
'kaufen',
'kostet',
'suche',
'such',
'finde',
'finden',
'analysegerät',
'analysegeraet',
'messgerät',
'messgeraet',
'pockettester',
'pocket tester',
'handmessgerät',
'handmessgeraet',
'analysator',
'analyzer',
'puffer',
'kalibrierpuffer',
'kalibrierlösung',
'kalibrierloesung',
'kalibrierung',
'chemie',
'reagenz',
'reagenzien',
'verbrauchsmaterial',
'zubehör',
'zubehoer',
'ersatzteil',
];
private const ADVISORY_SIGNALS = [
'passt',
'eignet',
'besser',
'besten',
'gut',
'gut für',
'gut fuer',
'passend für',
'passend fuer',
'geeignet',
'geeigent',
'empfiehl',
'empfehl',
];
private const ADVISORY_PRODUCT_SELECTION_PATTERNS = [
'/\bmit\s+welche(?:m|n|r|s)?\s+(?:testomat(?:en)?|pockettester|pocket\s+tester|analysegerät|analysegeraet|messgerät|messgeraet|analysator|analyzer)\b.*\b(?:messen|messung|überwach(?:en|ung)?|ueberwach(?:en|ung)?)\b/u',
'/\bwelche(?:r|s|n|m)?\s+(?:testomat(?:en)?|pockettester|pocket\s+tester|analysegerät|analysegeraet|messgerät|messgeraet|analysator|analyzer)\b.*\b(?:kann|können|koennen|misst|messen|überwacht|ueberwacht|eignet|geeignet|passt|gut|empfehl)\b.*\b(?:messen|messung|überwach(?:en|ung)?|ueberwach(?:en|ung)?)\b/u',
'/\b(?:testomat(?:en)?|pockettester|pocket\s+tester|analysegerät|analysegeraet|messgerät|messgeraet|analysator|analyzer)\b.*\b(?:für|fuer)\b.*\b(?:messung|messen|überwachung|ueberwachung)\b/u',
];
private const PRICE_TERMS = [
'euro',
'€',
'eur',
'teuer',
'preis',
'kosten',
'kostet',
];
private const COLOR_TERMS = [
'schwarz',
'weiß',
'weis',
'blau',
'grau',
'beige',
'rosa',
'pink',
'gruen',
'orange',
'braun',
];
private const SIZE_TOKEN_TERMS = [
'xs',
's',
'm',
'l',
'xl',
'xxl',
'xxxxl',
];
private const SIZE_TERMS = [
'größe',
'groesse',
'grösse',
];
private const SUPPORT_DIAGNOSTIC_PATTERNS = [
'/\bfehler\b/u',
'/\bfehlercode\b/u',
'/\berror\b/u',
'/\bstörung\b/u',
'/\bstoerung\b/u',
'/\balarm\b/u',
'/\bstörungsmeldung\b/u',
'/\bstoerungsmeldung\b/u',
'/\bmeldung\b/u',
'/\bwarnung\b/u',
'/\bwarncode\b/u',
'/\bcode\b/u',
'/\bwas bedeutet\b/u',
'/\bwarum\b/u',
'/\bblinkt\b/u',
'/\bzeigt\b/u',
'/\bzeigt an\b/u',
'/\bursache\b/u',
'/\bdiagnose\b/u',
'/\bservicefall\b/u',
'/\bproblem\b/u',
'/\bstörung beheben\b/u',
'/\bstoerung beheben\b/u',
'/\be\d{1,3}\b/u',
];
private const EXPLICIT_COMMERCE_INTENT_PATTERNS = [
'/\bshop\b/u',
'/\bpreis\b/u',
'/\bkosten\b/u',
'/\bkostet\b/u',
'/\bkaufen\b/u',
'/\bbestellen\b/u',
'/\bprodukt\b/u',
'/\bartikel\b/u',
'/\bsku\b/u',
'/\bonline\b/u',
'/\bchemie\b/u',
'/\breagenz(?:ien)?\b/u',
'/\bverbrauchsmaterial(?:ien)?\b/u',
'/\bzubehör\b/u',
'/\bzubehoer\b/u',
'/\bersatzteil(?:e)?\b/u',
];
/**
* @param array<string, mixed> $config
*/
@@ -163,25 +16,25 @@ final class CommerceIntentConfig
/** @return string[] */
public function getStrongSignalsList(): array
{
return $this->stringList('strong_signals', self::STRONG_SIGNALS);
return $this->requiredStringList('strong_signals');
}
/** @return string[] */
public function getAdvisorySignals(): array
{
return $this->stringList('advisory_signals', self::ADVISORY_SIGNALS);
return $this->requiredStringList('advisory_signals');
}
/** @return string[] */
public function getAdvisoryProductSelectionPatterns(): array
{
return $this->stringList('advisory_product_selection_patterns', self::ADVISORY_PRODUCT_SELECTION_PATTERNS);
return $this->requiredStringList('advisory_product_selection_patterns');
}
/** @return string[] */
public function getPriceTerms(): array
{
return $this->stringList('price_terms', self::PRICE_TERMS);
return $this->requiredStringList('price_terms');
}
public function getPricePattern(): string
@@ -192,7 +45,7 @@ final class CommerceIntentConfig
/** @return string[] */
public function getColorTerms(): array
{
return $this->stringList('color_terms', self::COLOR_TERMS);
return $this->requiredStringList('color_terms');
}
public function getColorPattern(): string
@@ -203,7 +56,7 @@ final class CommerceIntentConfig
/** @return string[] */
public function getSizeTokenTerms(): array
{
return $this->stringList('size_token_terms', self::SIZE_TOKEN_TERMS);
return $this->requiredStringList('size_token_terms');
}
public function getSizeTokenPattern(): string
@@ -214,7 +67,7 @@ final class CommerceIntentConfig
/** @return string[] */
public function getSizeTerms(): array
{
return $this->stringList('size_terms', self::SIZE_TERMS);
return $this->requiredStringList('size_terms');
}
public function getSizePattern(): string
@@ -224,157 +77,192 @@ final class CommerceIntentConfig
public function getSizeExtractionPattern(): string
{
return '/\b(?:' . $this->getSizePattern() . ')\s*([a-z0-9.-]+)\b/u';
return $this->renderPatternTemplate('patterns.size_extraction_template', [
'size_pattern' => $this->getSizePattern(),
]);
}
/** @return string[] */
public function getSupportDiagnosticPatterns(): array
{
return $this->stringList('support_diagnostic_patterns', self::SUPPORT_DIAGNOSTIC_PATTERNS);
return $this->requiredStringList('support_diagnostic_patterns');
}
/** @return string[] */
public function getExplicitCommerceIntentPatterns(): array
{
return $this->stringList('explicit_commerce_intent_patterns', self::EXPLICIT_COMMERCE_INTENT_PATTERNS);
return $this->requiredStringList('explicit_commerce_intent_patterns');
}
public function getSkuLikePattern(): string
{
return '/\b\d{4,10}\b/u';
return $this->requiredString('patterns.sku_like');
}
public function getPriceValuePattern(): string
{
return '/\b\d+(?:[.,]\d+)?\s*(?:' . $this->getPricePattern() . ')\b/u';
return $this->renderPatternTemplate('patterns.price_value_template', [
'price_pattern' => $this->getPricePattern(),
]);
}
public function getSizeValuePattern(): string
{
return '/\b(?:' . $this->getSizePattern() . ')\s*[a-z0-9.-]+\b/u';
return $this->renderPatternTemplate('patterns.size_value_template', [
'size_pattern' => $this->getSizePattern(),
]);
}
public function getSizeTokenValuePattern(): string
{
return '/\b(?:' . $this->getSizeTokenPattern() . ')\b/u';
return $this->renderPatternTemplate('patterns.size_token_value_template', [
'size_token_pattern' => $this->getSizeTokenPattern(),
]);
}
public function getColorValuePattern(): string
{
return '/\b(?:' . $this->getColorPattern() . ')\b/u';
return $this->renderPatternTemplate('patterns.color_value_template', [
'color_pattern' => $this->getColorPattern(),
]);
}
public function getSupportOrDiagnosticSignalLabel(): string
{
return 'support_or_diagnostic';
return $this->requiredString('labels.support_or_diagnostic_signal');
}
public function getSkuSignalLabel(): string
{
return 'sku';
return $this->requiredString('labels.sku_signal');
}
public function getPriceSignalLabel(): string
{
return 'price';
return $this->requiredString('labels.price_signal');
}
public function getSizeSignalLabel(): string
{
return 'size';
return $this->requiredString('labels.size_signal');
}
public function getSizeTokenSignalLabel(): string
{
return 'size_token';
return $this->requiredString('labels.size_token_signal');
}
public function getColorSignalLabel(): string
{
return 'color';
return $this->requiredString('labels.color_signal');
}
public function getAdvisorySignalPrefix(): string
{
return 'advisory:';
return $this->requiredString('labels.advisory_signal_prefix');
}
public function getAdvisoryProductSelectionSignalLabel(): string
{
return 'advisory_product_selection';
return $this->requiredString('labels.advisory_product_selection_signal');
}
public function getProductSearchMinScore(): int
{
return 3;
return $this->requiredInt('scores.product_search_min_score');
}
public function getAdvisoryProductSearchMinScore(): int
{
return 2;
return $this->requiredInt('scores.advisory_product_search_min_score');
}
public function getStrongSignalScore(): int
{
return 3;
return $this->requiredInt('scores.strong_signal_score');
}
public function getSkuSignalScore(): int
{
return 2;
return $this->requiredInt('scores.sku_signal_score');
}
public function getPriceSignalScore(): int
{
return 2;
return $this->requiredInt('scores.price_signal_score');
}
public function getSizeSignalScore(): int
{
return 2;
return $this->requiredInt('scores.size_signal_score');
}
public function getSizeTokenSignalScore(): int
{
return 1;
return $this->requiredInt('scores.size_token_signal_score');
}
public function getColorSignalScore(): int
{
return 1;
return $this->requiredInt('scores.color_signal_score');
}
public function getAdvisorySignalScore(): int
{
return 1;
return $this->requiredInt('scores.advisory_signal_score');
}
public function getAdvisoryProductSelectionSignalScore(): int
{
return 3;
return $this->requiredInt('scores.advisory_product_selection_signal_score');
}
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';
return $this->requiredString('patterns.model_like_product');
}
public function getModelLikeProductSignalLabel(): string
{
return 'model_like_product';
return $this->requiredString('labels.model_like_product_signal');
}
public function getModelLikeProductSignalScore(): int
{
return 3;
return $this->requiredInt('scores.model_like_product_signal_score');
}
private function requiredInt(string $key): int
{
$value = $this->getRequiredValue($key);
if (!is_int($value)) {
throw new \InvalidArgumentException(sprintf('RetrieX commerce intent config key "%s" must be an integer.', $key));
}
return $value;
}
private function requiredString(string $key): string
{
$value = $this->getRequiredValue($key);
if (!is_scalar($value)) {
throw new \InvalidArgumentException(sprintf('RetrieX commerce intent config key "%s" must be a string.', $key));
}
$value = trim((string) $value);
if ($value === '') {
throw new \InvalidArgumentException(sprintf('RetrieX commerce intent config key "%s" must not be empty.', $key));
}
return $value;
}
/** @return string[] */
private function stringList(string $key, array $default): array
private function requiredStringList(string $key): array
{
$value = $this->config[$key] ?? $default;
$value = $this->getRequiredValue($key);
if (!is_array($value)) {
return $default;
throw new \InvalidArgumentException(sprintf('RetrieX commerce intent config key "%s" must be a list.', $key));
}
$out = [];
@@ -391,6 +279,43 @@ final class CommerceIntentConfig
$out[] = $item;
}
return $out !== [] ? $out : $default;
if ($out === []) {
throw new \InvalidArgumentException(sprintf('RetrieX commerce intent config key "%s" must contain at least one value.', $key));
}
return $out;
}
/**
* @param array<string, string> $replacements
*/
private function renderPatternTemplate(string $key, array $replacements): string
{
$template = $this->requiredString($key);
$replace = [];
foreach ($replacements as $placeholder => $value) {
$replace['{' . $placeholder . '}'] = $value;
}
$pattern = strtr($template, $replace);
if (preg_match('/\{[A-Za-z_][A-Za-z0-9_]*\}/', $pattern) === 1) {
throw new \InvalidArgumentException(sprintf('RetrieX commerce intent pattern template "%s" contains unresolved placeholders.', $key));
}
return $pattern;
}
private function getRequiredValue(string $key): mixed
{
$parts = explode('.', $key);
$value = $this->config;
foreach ($parts as $part) {
if (!is_array($value) || !array_key_exists($part, $value)) {
throw new \InvalidArgumentException(sprintf('Missing required RetrieX commerce intent config key "%s".', $key));
}
$value = $value[$part];
}
return $value;
}
}

View File

@@ -645,12 +645,33 @@ final readonly class RetriexEffectiveConfigProvider
'commerce' => [
'strong_signals' => $this->commerceIntentConfig->getStrongSignalsList(),
'advisory_signals' => $this->commerceIntentConfig->getAdvisorySignals(),
'advisory_product_selection_patterns' => $this->commerceIntentConfig->getAdvisoryProductSelectionPatterns(),
'price_terms' => $this->commerceIntentConfig->getPriceTerms(),
'color_terms' => $this->commerceIntentConfig->getColorTerms(),
'size_token_terms' => $this->commerceIntentConfig->getSizeTokenTerms(),
'size_terms' => $this->commerceIntentConfig->getSizeTerms(),
'support_diagnostic_patterns' => $this->commerceIntentConfig->getSupportDiagnosticPatterns(),
'explicit_commerce_intent_patterns' => $this->commerceIntentConfig->getExplicitCommerceIntentPatterns(),
'patterns' => [
'sku_like' => $this->commerceIntentConfig->getSkuLikePattern(),
'price_value' => $this->commerceIntentConfig->getPriceValuePattern(),
'size_extraction' => $this->commerceIntentConfig->getSizeExtractionPattern(),
'size_value' => $this->commerceIntentConfig->getSizeValuePattern(),
'size_token_value' => $this->commerceIntentConfig->getSizeTokenValuePattern(),
'color_value' => $this->commerceIntentConfig->getColorValuePattern(),
'model_like_product' => $this->commerceIntentConfig->getModelLikeProductPattern(),
],
'labels' => [
'support_or_diagnostic_signal' => $this->commerceIntentConfig->getSupportOrDiagnosticSignalLabel(),
'sku_signal' => $this->commerceIntentConfig->getSkuSignalLabel(),
'price_signal' => $this->commerceIntentConfig->getPriceSignalLabel(),
'size_signal' => $this->commerceIntentConfig->getSizeSignalLabel(),
'size_token_signal' => $this->commerceIntentConfig->getSizeTokenSignalLabel(),
'color_signal' => $this->commerceIntentConfig->getColorSignalLabel(),
'advisory_signal_prefix' => $this->commerceIntentConfig->getAdvisorySignalPrefix(),
'advisory_product_selection_signal' => $this->commerceIntentConfig->getAdvisoryProductSelectionSignalLabel(),
'model_like_product_signal' => $this->commerceIntentConfig->getModelLikeProductSignalLabel(),
],
'thresholds' => [
'product_search_min_score' => $this->commerceIntentConfig->getProductSearchMinScore(),
'advisory_product_search_min_score' => $this->commerceIntentConfig->getAdvisoryProductSearchMinScore(),
@@ -661,6 +682,7 @@ final readonly class RetriexEffectiveConfigProvider
'size_token_signal_score' => $this->commerceIntentConfig->getSizeTokenSignalScore(),
'color_signal_score' => $this->commerceIntentConfig->getColorSignalScore(),
'advisory_signal_score' => $this->commerceIntentConfig->getAdvisorySignalScore(),
'advisory_product_selection_signal_score' => $this->commerceIntentConfig->getAdvisoryProductSelectionSignalScore(),
'model_like_product_signal_score' => $this->commerceIntentConfig->getModelLikeProductSignalScore(),
],
],
@@ -681,7 +703,6 @@ final readonly class RetriexEffectiveConfigProvider
];
}
/** @return array<string, mixed> */
private function languageConfig(): array
{
return ['stopwords' => $this->stopWordsConfig->getStopWords()];