From 00ebe2fd7353fd0fa6c72138342f8cdc4d5fa410 Mon Sep 17 00:00:00 2001 From: team2 Date: Sun, 26 Apr 2026 08:34:45 +0200 Subject: [PATCH] central config part 2 --- RETRIEX_CONFIG_CENTRALIZATION_FIX_README.md | 16 + config/retriex/intent.yaml | 216 +++++++++++++ config/retriex/vocabulary.yaml | 50 +++ config/services.yaml | 15 + src/Config/CommerceIntentConfig.php | 334 +++++++++++--------- src/Config/IntentLightConfig.php | 112 ++++--- src/Config/SalesIntentConfig.php | 157 ++++++--- src/Config/SearchRepairConfig.php | 170 ++++++---- 8 files changed, 782 insertions(+), 288 deletions(-) create mode 100644 RETRIEX_CONFIG_CENTRALIZATION_FIX_README.md create mode 100644 config/retriex/intent.yaml diff --git a/RETRIEX_CONFIG_CENTRALIZATION_FIX_README.md b/RETRIEX_CONFIG_CENTRALIZATION_FIX_README.md new file mode 100644 index 0000000..5246333 --- /dev/null +++ b/RETRIEX_CONFIG_CENTRALIZATION_FIX_README.md @@ -0,0 +1,16 @@ +# RetrieX configuration centralization fix + +This patch centralizes additional low-risk word and pattern lists without changing their defaults. + +## Changed areas + +- `SearchRepairConfig` now reads repair vocabulary from `config/retriex/vocabulary.yaml` via `DomainVocabularyConfig`. +- `CommerceIntentConfig`, `IntentLightConfig`, and `SalesIntentConfig` now read their lists from `config/retriex/intent.yaml`. +- Existing PHP defaults remain in the classes as fallbacks. +- Existing scalar thresholds and prompt wording are unchanged. + +## Safety notes + +The YAML values mirror the previous PHP defaults 1:1. The patch is meant to simplify maintenance and configuration, not to broaden or alter matching behavior. + +After installing, clear the Symfony cache and run the known 1.4.2 regression prompts. diff --git a/config/retriex/intent.yaml b/config/retriex/intent.yaml new file mode 100644 index 0000000..27e653f --- /dev/null +++ b/config/retriex/intent.yaml @@ -0,0 +1,216 @@ +# Intent vocabulary and pattern configuration. +# Lists mirror the previous PHP defaults exactly; PHP defaults remain as fallback. +parameters: + retriex.intent.commerce.config: + strong_signals: + - shop + - alle + - preis + - kunde + - online + - produkt + - artikel + - sku + - kaufen + - kostet + - suche + - such + - finde + - finden + - analysegerät + - analysegeraet + - messgerät + - messgeraet + - analysator + - analyzer + - puffer + - kalibrierpuffer + - kalibrierlösung + - kalibrierloesung + - kalibrierung + - chemie + - reagenz + - reagenzien + - verbrauchsmaterial + - zubehör + - zubehoer + - ersatzteil + advisory_signals: + - passt + - eignet + - besser + - besten + - gut für + - gut fuer + - passend für + - passend fuer + - geeignet + - geeigent + - empfiehl + - empfehl + price_terms: + - euro + - € + - eur + - teuer + - preis + - kosten + - kostet + color_terms: + - schwarz + - weiß + - weis + - blau + - grau + - beige + - rosa + - pink + - gruen + - orange + - braun + size_token_terms: + - xs + - s + - m + - l + - xl + - xxl + - xxxxl + size_terms: + - größe + - groesse + - grösse + 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' + 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' + + retriex.intent.light.config: + quantity_words: + - alle + - sämtliche + - saemtliche + - mehrere + - verschiedene + - einige + - viele + - optionen + - möglichkeiten + - moeglichkeiten + - varianten + - arten + - modelle + - funktionen + - punkte + - schritte + - kategorien + - übersicht + - uebersicht + strong_patterns: + - '/\bliste(n)?\b/u' + - '/\bauflisten\b/u' + - '/\baufz(a|ä)hl(en)?\b/u' + - '/\bnenn(e)?\b/u' + - '/\bzeig(e)?\b/u' + - '/\bwelche\s+sind\b/u' + - '/\bwelche\s+gibt\s+es\b/u' + - '/\bwas\s+sind\b/u' + - '/\bwie\s+viele\b/u' + - '/\branking\b/u' + - '/\btop\s*\d+\b/u' + + retriex.intent.sales.config: + sales_signals: + - preis + - preise + - kosten + - lizenz + - lizenzmodell + - tarif + - tarife + - gebuehr + - gebühr + - monatlich + - jaehrlich + - jährlich + - abo + - subscription + comparison_signals: + - '/\bvergleich(en)?\b/u' + - '/\bvs\b/u' + - '/\bgegenueber\b/u' + - '/\balternative(n)?\b/u' + - '/\bunterschied(e)?\b/u' + - '/\bbesser\b/u' + objection_signals: + - problem + - risiko + - nachteil + - datenschutz + - dsgvo + - sicherheit + - compliance + - kritik + - zweifel + - unsicher + implementation_signals: + - implementierung + - implementieren + - integration + - integrieren + - einführung + - einfuehrung + - aufwand + - setup + - rollout + - migration + - installation + - api + - schnittstelle + roi_signals: + - roi + - rentabilitaet + - rentabilität + - business case + - einsparung + - kosten senken + - umsatz steigern + - effizienz steigern diff --git a/config/retriex/vocabulary.yaml b/config/retriex/vocabulary.yaml index f1b094b..e26da38 100644 --- a/config/retriex/vocabulary.yaml +++ b/config/retriex/vocabulary.yaml @@ -2,6 +2,7 @@ # Views preserve the previous 1.4.2-tuned ordering exactly; per-service configs may still override them. parameters: retriex.commerce_query.config: {} + retriex.search_repair.config: {} retriex.vocabulary.config: classes: device: @@ -485,6 +486,55 @@ parameters: - überwachung - online - monitor + search_repair: + generic_candidate_tokens: + add: + - wasser + - messgerät + - messgeraet + - produkt + - geräte + - geraete + - gerät + - geraet + - resthärte + - resthaerte + - preis + - infos + - wissen + accessory_candidate_terms: + add: + - indikator + - indicator + - reagenz + - reagent + - kit + - set + accessory_or_bundle_terms: + add: + - passend + - passende + - zubehor + - zubehör + - dazu + - zusatz + - erganzung + - ergänzung + - indikator + - reagenz + - kit + - set + - auch\s+das + - mit\s+preis\s+und\s+allen\s+infos + specificity_boost_terms: + add: + - indikator + - indicator + - testomat + - tritromat + - titromat + - reagenz + - reagent prompt: technical_product_keywords: add: diff --git a/config/services.yaml b/config/services.yaml index 11616d0..d475547 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -10,6 +10,7 @@ imports: - { resource: 'retriex/language.yaml' } - { resource: 'retriex/query_enrichment.yaml' } - { resource: 'retriex/vocabulary.yaml' } + - { resource: 'retriex/intent.yaml' } # ------------------------------------------------------------ # Parameters @@ -183,6 +184,10 @@ services: App\Intent\CommerceIntentLite: ~ + App\Config\CommerceIntentConfig: + arguments: + $config: '%retriex.intent.commerce.config%' + App\Config\CommerceQueryParserConfig: arguments: $config: '%retriex.commerce_query.config%' @@ -195,9 +200,19 @@ services: $enabled: '%retriex.commerce.search_repair.enabled%' $maxRepairQueries: '%retriex.commerce.search_repair.max_queries%' $minPrimaryResultsWithoutRepair: '%retriex.commerce.search_repair.min_primary_results_without_repair%' + $config: '%retriex.search_repair.config%' + $vocabulary: '@App\Config\DomainVocabularyConfig' App\Commerce\SearchRepairService: ~ + App\Config\IntentLightConfig: + arguments: + $config: '%retriex.intent.light.config%' + + App\Config\SalesIntentConfig: + arguments: + $config: '%retriex.intent.sales.config%' + App\Shopware\ShopwareCriteriaBuilder: ~ App\Shopware\StoreApiClient: diff --git a/src/Config/CommerceIntentConfig.php b/src/Config/CommerceIntentConfig.php index 84a6054..03d8f64 100644 --- a/src/Config/CommerceIntentConfig.php +++ b/src/Config/CommerceIntentConfig.php @@ -6,82 +6,165 @@ 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', + '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 für', + 'gut fuer', + 'passend für', + 'passend fuer', + 'geeignet', + 'geeigent', + 'empfiehl', + 'empfehl', + ]; + + 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', + ]; + /** - * @return string[] + * @param array $config */ + public function __construct(private readonly array $config = []) + { + } + + /** @return string[] */ public function getStrongSignalsList(): array { - return [ - 'shop', - 'alle', - 'preis', - 'kunde', - 'online', - 'produkt', - 'artikel', - 'sku', - 'kaufen', - 'kostet', - 'suche', - 'such', - 'finde', - 'finden', - 'analysegerät', - 'analysegeraet', - 'messgerät', - 'messgeraet', - 'analysator', - 'analyzer', - 'puffer', - 'kalibrierpuffer', - 'kalibrierlösung', - 'kalibrierloesung', - 'kalibrierung', - 'chemie', - 'reagenz', - 'reagenzien', - 'verbrauchsmaterial', - 'zubehör', - 'zubehoer', - 'ersatzteil', - ]; + return $this->stringList('strong_signals', self::STRONG_SIGNALS); } - /** - * @return string[] - */ + /** @return string[] */ public function getAdvisorySignals(): array { - return [ - 'passt', - 'eignet', - 'besser', - 'besten', - 'gut für', - 'gut fuer', - 'passend für', - 'passend fuer', - 'geeignet', - 'geeigent', - 'empfiehl', - 'empfehl', - ]; + return $this->stringList('advisory_signals', self::ADVISORY_SIGNALS); } - /** - * @return string[] - */ + /** @return string[] */ public function getPriceTerms(): array { - return [ - 'euro', - '€', - 'eur', - 'teuer', - 'preis', - 'kosten', - 'kostet', - ]; + return $this->stringList('price_terms', self::PRICE_TERMS); } public function getPricePattern(): string @@ -89,24 +172,10 @@ final class CommerceIntentConfig return implode('|', $this->getPriceTerms()); } - /** - * @return string[] - */ + /** @return string[] */ public function getColorTerms(): array { - return [ - 'schwarz', - 'weiß', - 'weis', - 'blau', - 'grau', - 'beige', - 'rosa', - 'pink', - 'gruen', - 'orange', - 'braun', - ]; + return $this->stringList('color_terms', self::COLOR_TERMS); } public function getColorPattern(): string @@ -114,20 +183,10 @@ final class CommerceIntentConfig return implode('|', $this->getColorTerms()); } - /** - * @return string[] - */ + /** @return string[] */ public function getSizeTokenTerms(): array { - return [ - 'xs', - 's', - 'm', - 'l', - 'xl', - 'xxl', - 'xxxxl', - ]; + return $this->stringList('size_token_terms', self::SIZE_TOKEN_TERMS); } public function getSizeTokenPattern(): string @@ -135,16 +194,10 @@ final class CommerceIntentConfig return implode('|', $this->getSizeTokenTerms()); } - /** - * @return string[] - */ + /** @return string[] */ public function getSizeTerms(): array { - return [ - 'größe', - 'groesse', - 'grösse', - ]; + return $this->stringList('size_terms', self::SIZE_TERMS); } public function getSizePattern(): string @@ -157,62 +210,16 @@ final class CommerceIntentConfig return '/\b(?:' . $this->getSizePattern() . ')\s*([a-z0-9.-]+)\b/u'; } - /** - * @return string[] - */ + /** @return string[] */ public function getSupportDiagnosticPatterns(): array { - return [ - '/\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', - ]; + return $this->stringList('support_diagnostic_patterns', self::SUPPORT_DIAGNOSTIC_PATTERNS); } - /** - * @return string[] - */ + /** @return string[] */ public function getExplicitCommerceIntentPatterns(): array { - return [ - '/\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', - ]; + return $this->stringList('explicit_commerce_intent_patterns', self::EXPLICIT_COMMERCE_INTENT_PATTERNS); } public function getSkuLikePattern(): string @@ -334,4 +341,29 @@ final class CommerceIntentConfig { return 3; } -} \ No newline at end of file + + /** @return string[] */ + private function stringList(string $key, array $default): array + { + $value = $this->config[$key] ?? $default; + if (!is_array($value)) { + return $default; + } + + $out = []; + foreach ($value as $item) { + if (!is_scalar($item)) { + continue; + } + + $item = trim((string) $item); + if ($item === '' || in_array($item, $out, true)) { + continue; + } + + $out[] = $item; + } + + return $out !== [] ? $out : $default; + } +} diff --git a/src/Config/IntentLightConfig.php b/src/Config/IntentLightConfig.php index 9a21fac..dbaf323 100644 --- a/src/Config/IntentLightConfig.php +++ b/src/Config/IntentLightConfig.php @@ -1,50 +1,90 @@ $config + */ + public function __construct(private readonly array $config = []) { - return [ - 'alle', - 'sämtliche', - 'saemtliche', - 'mehrere', - 'verschiedene', - 'einige', - 'viele', - 'optionen', - 'möglichkeiten', - 'moeglichkeiten', - 'varianten', - 'arten', - 'modelle', - 'funktionen', - 'punkte', - 'schritte', - 'kategorien', - 'übersicht', - 'uebersicht', - ]; } + /** @return string[] */ + public function getQuantityWords(): array + { + return $this->stringList('quantity_words', self::QUANTITY_WORDS); + } + + /** @return string[] */ public function getStrongPatterns(): array { - return [ - '/\bliste(n)?\b/u', - '/\bauflisten\b/u', - '/\baufz(a|ä)hl(en)?\b/u', - '/\bnenn(e)?\b/u', - '/\bzeig(e)?\b/u', - '/\bwelche\s+sind\b/u', - '/\bwelche\s+gibt\s+es\b/u', - '/\bwas\s+sind\b/u', - '/\bwie\s+viele\b/u', - '/\branking\b/u', - '/\btop\s*\d+\b/u', - ]; + return $this->stringList('strong_patterns', self::STRONG_PATTERNS); } -} \ No newline at end of file + + /** @return string[] */ + private function stringList(string $key, array $default): array + { + $value = $this->config[$key] ?? $default; + if (!is_array($value)) { + return $default; + } + + $out = []; + foreach ($value as $item) { + if (!is_scalar($item)) { + continue; + } + + $item = trim((string) $item); + if ($item === '' || in_array($item, $out, true)) { + continue; + } + + $out[] = $item; + } + + return $out !== [] ? $out : $default; + } +} diff --git a/src/Config/SalesIntentConfig.php b/src/Config/SalesIntentConfig.php index fe6d74c..bda11e3 100644 --- a/src/Config/SalesIntentConfig.php +++ b/src/Config/SalesIntentConfig.php @@ -1,65 +1,142 @@ $config + */ + public function __construct(private readonly array $config = []) + { + } + + /** @return string[] */ public function getSalesSignals(): array { - return [ - 'preis', 'preise', 'kosten', 'lizenz', 'lizenzmodell', - 'tarif', 'tarife', 'gebuehr', 'gebühr', - 'monatlich', 'jaehrlich', 'jährlich', 'abo', 'subscription' - ]; + return $this->stringList('sales_signals', self::SALES_SIGNALS); } + /** @return string[] */ public function getComparisonSignals(): array { - return [ - '/\bvergleich(en)?\b/u', - '/\bvs\b/u', - '/\bgegenueber\b/u', - '/\balternative(n)?\b/u', - '/\bunterschied(e)?\b/u', - '/\bbesser\b/u' - ]; + return $this->stringList('comparison_signals', self::COMPARISON_SIGNALS); } + /** @return string[] */ public function getObjectionSignals(): array { - return [ - 'problem', 'risiko', 'nachteil', 'datenschutz', - 'dsgvo', 'sicherheit', 'compliance', - 'kritik', 'zweifel', 'unsicher' - ]; - } - public function getImplementationSignals(): array - { - return [ - 'implementierung', 'implementieren', - 'integration', 'integrieren', - 'einführung', 'einfuehrung', - 'aufwand', 'setup', 'rollout', - 'migration', 'installation', - 'api', 'schnittstelle' - ]; - } - public function getRoiSignals(): array - { - return [ - 'roi', 'rentabilitaet', 'rentabilität', - 'business case', 'einsparung', - 'kosten senken', 'umsatz steigern', - 'effizienz steigern' - ]; + return $this->stringList('objection_signals', self::OBJECTION_SIGNALS); } -} \ No newline at end of file + /** @return string[] */ + public function getImplementationSignals(): array + { + return $this->stringList('implementation_signals', self::IMPLEMENTATION_SIGNALS); + } + + /** @return string[] */ + public function getRoiSignals(): array + { + return $this->stringList('roi_signals', self::ROI_SIGNALS); + } + + /** @return string[] */ + private function stringList(string $key, array $default): array + { + $value = $this->config[$key] ?? $default; + if (!is_array($value)) { + return $default; + } + + $out = []; + foreach ($value as $item) { + if (!is_scalar($item)) { + continue; + } + + $item = trim((string) $item); + if ($item === '' || in_array($item, $out, true)) { + continue; + } + + $out[] = $item; + } + + return $out !== [] ? $out : $default; + } +} diff --git a/src/Config/SearchRepairConfig.php b/src/Config/SearchRepairConfig.php index fee9bea..5c94993 100644 --- a/src/Config/SearchRepairConfig.php +++ b/src/Config/SearchRepairConfig.php @@ -6,10 +6,67 @@ namespace App\Config; final class SearchRepairConfig { + private const GENERIC_CANDIDATE_TOKENS = [ + 'wasser', + 'messgerät', + 'messgeraet', + 'produkt', + 'geräte', + 'geraete', + 'gerät', + 'geraet', + 'resthärte', + 'resthaerte', + 'preis', + 'infos', + 'wissen', + ]; + + private const ACCESSORY_CANDIDATE_TERMS = [ + 'indikator', + 'indicator', + 'reagenz', + 'reagent', + 'kit', + 'set', + ]; + + private const ACCESSORY_OR_BUNDLE_TERMS = [ + 'passend', + 'passende', + 'zubehor', + 'zubehör', + 'dazu', + 'zusatz', + 'erganzung', + 'ergänzung', + 'indikator', + 'reagenz', + 'kit', + 'set', + 'auch\s+das', + 'mit\s+preis\s+und\s+allen\s+infos', + ]; + + private const SPECIFICITY_BOOST_TERMS = [ + 'indikator', + 'indicator', + 'testomat', + 'tritromat', + 'titromat', + 'reagenz', + 'reagent', + ]; + + /** + * @param array $config + */ public function __construct( private readonly bool $enabled = true, private readonly int $maxRepairQueries = 3, private readonly int $minPrimaryResultsWithoutRepair = 2, + private readonly array $config = [], + private readonly ?DomainVocabularyConfig $vocabulary = null, ) { } @@ -58,26 +115,13 @@ final class SearchRepairConfig return '/\b(?:' . implode('|', $this->getSpecificityBoostTerms()) . ')\b/iu'; } - /** - * @return string[] - */ + /** @return string[] */ public function getGenericCandidateTokens(): array { - return [ - 'wasser', - 'messgerät', - 'messgeraet', - 'produkt', - 'geräte', - 'geraete', - 'gerät', - 'geraet', - 'resthärte', - 'resthaerte', - 'preis', - 'infos', - 'wissen', - ]; + return $this->stringList( + 'generic_candidate_tokens', + $this->vocabularyView('search_repair.generic_candidate_tokens', self::GENERIC_CANDIDATE_TOKENS) + ); } public function getSanitizeTrimCharacters(): string @@ -155,57 +199,61 @@ final class SearchRepairConfig return 4; } - /** - * @return string[] - */ + /** @return string[] */ public function getAccessoryCandidateTerms(): array { - return [ - 'indikator', - 'indicator', - 'reagenz', - 'reagent', - 'kit', - 'set', - ]; + return $this->stringList( + 'accessory_candidate_terms', + $this->vocabularyView('search_repair.accessory_candidate_terms', self::ACCESSORY_CANDIDATE_TERMS) + ); } - /** - * @return string[] - */ + /** @return string[] */ public function getAccessoryOrBundleTerms(): array { - return [ - 'passend', - 'passende', - 'zubehor', - 'zubehör', - 'dazu', - 'zusatz', - 'erganzung', - 'ergänzung', - 'indikator', - 'reagenz', - 'kit', - 'set', - 'auch\s+das', - 'mit\s+preis\s+und\s+allen\s+infos', - ]; + return $this->stringList( + 'accessory_or_bundle_terms', + $this->vocabularyView('search_repair.accessory_or_bundle_terms', self::ACCESSORY_OR_BUNDLE_TERMS) + ); } - /** - * @return string[] - */ + /** @return string[] */ public function getSpecificityBoostTerms(): array { - return [ - 'indikator', - 'indicator', - 'testomat', - 'tritromat', - 'titromat', - 'reagenz', - 'reagent', - ]; + return $this->stringList( + 'specificity_boost_terms', + $this->vocabularyView('search_repair.specificity_boost_terms', self::SPECIFICITY_BOOST_TERMS) + ); } -} \ No newline at end of file + + /** @return string[] */ + private function vocabularyView(string $path, array $fallback): array + { + return $this->vocabulary?->view($path, $fallback) ?? $fallback; + } + + /** @return string[] */ + private function stringList(string $key, array $default): array + { + $value = $this->config[$key] ?? $default; + if (!is_array($value)) { + return $default; + } + + $out = []; + foreach ($value as $item) { + if (!is_scalar($item)) { + continue; + } + + $item = trim((string) $item); + if ($item === '' || in_array($item, $out, true)) { + continue; + } + + $out[] = $item; + } + + return $out !== [] ? $out : $default; + } +}