This commit is contained in:
team 1
2026-04-24 18:54:25 +02:00
parent 372a6797fa
commit c439fb99d6
12 changed files with 1126 additions and 336 deletions

View File

@@ -7,451 +7,461 @@ namespace App\Config;
final class ShopServiceConfig
{
public const DEVICE_QUERY_KEYWORDS = [
'analysegerät',
'analysegeraet',
'analysegeräte',
'analysegeraete',
'messgerät',
'messgeraet',
'messgeräte',
'messgeraete',
'analysator',
'analysatoren',
'analyzer',
'gerät',
'geraet',
'geräte',
'geraete',
'monitor',
'monitore',
'controller',
'gerät für',
'geraet fuer',
'geräte für',
'geraete fuer',
'system',
'systeme',
'anlage',
'anlagen',
'analysegerät', 'analysegeraet', 'analysegeräte', 'analysegeraete',
'messgerät', 'messgeraet', 'messgeräte', 'messgeraete',
'analysator', 'analysatoren', 'analyzer', 'gerät', 'geraet', 'geräte',
'geraete', 'monitor', 'monitore', 'controller', 'gerät für',
'geraet fuer', 'geräte für', 'geraete fuer', 'system', 'systeme',
'anlage', 'anlagen',
];
public const ACCESSORY_QUERY_KEYWORDS = [
'zubehör',
'zubehor',
'reagenz',
'reagenzien',
'reagent',
'indikator',
'indikatoren',
'indicator',
'kit',
'set',
'ersatz',
'ersatzteil',
'ersatzteile',
'verbrauchsmaterial',
'consumable',
'dazu',
'passend',
'passende',
'passendes',
'nachfüll',
'nachfuell',
'refill',
'filter',
'pumpenkopf',
'motorblock',
'service set',
'serviceset',
'service-set',
'zubehör', 'zubehor', 'reagenz', 'reagenzien', 'reagent', 'indikator',
'indikatoren', 'indicator', 'kit', 'set', 'ersatz', 'ersatzteil',
'ersatzteile', 'verbrauchsmaterial', 'consumable', 'dazu', 'passend',
'passende', 'passendes', 'nachfüll', 'nachfuell', 'refill', 'filter',
'pumpenkopf', 'motorblock', 'service set', 'serviceset', 'service-set',
];
public const ACCESSORY_PRODUCT_KEYWORDS = [
'reagenz',
'reagenzien',
'reagent',
'indikator',
'indikatoren',
'indicator',
'kit',
'set',
'verbrauchsmaterial',
'consumable',
'zubehör',
'zubehor',
'ersatz',
'ersatzteil',
'ersatzteile',
'nachfüll',
'nachfuell',
'refill',
'lösung',
'loesung',
'solution',
'teststreifen',
'test strip',
'filter',
'pumpenkopf',
'motorblock',
'service set',
'serviceset',
'service-set',
'reagenz', 'reagenzien', 'reagent', 'indikator', 'indikatoren',
'indicator', 'kit', 'set', 'verbrauchsmaterial', 'consumable',
'zubehör', 'zubehor', 'ersatz', 'ersatzteil', 'ersatzteile',
'nachfüll', 'nachfuell', 'refill', 'lösung', 'loesung', 'solution',
'teststreifen', 'test strip', 'filter', 'pumpenkopf', 'motorblock',
'service set', 'serviceset', 'service-set',
];
public const DEVICE_PRODUCT_KEYWORDS = [
'analysegerät',
'analysegeraet',
'analysegeräte',
'analysegeraete',
'messgerät',
'messgeraet',
'messgeräte',
'messgeraete',
'analysator',
'analysatoren',
'analyzer',
'monitor',
'monitore',
'controller',
'online-analysator',
'online analysator',
'online-analysegerät',
'online analysegeraet',
'online-analysegeräte',
'online analysegeraete',
'online analyzer',
'online monitor',
'system',
'systeme',
'anlage',
'anlagen',
'gerät',
'geraet',
'geräte',
'geraete',
'analysegerät', 'analysegeraet', 'analysegeräte', 'analysegeraete',
'messgerät', 'messgeraet', 'messgeräte', 'messgeraete',
'analysator', 'analysatoren', 'analyzer', 'monitor', 'monitore',
'controller', 'online-analysator', 'online analysator',
'online-analysegerät', 'online analysegeraet', 'online-analysegeräte',
'online analysegeraete', 'online analyzer', 'online monitor', 'system',
'systeme', 'anlage', 'anlagen', 'gerät', 'geraet', 'geräte', 'geraete',
];
private const DEVICE_FOCUS_KEYWORDS = [
'geräte', 'geraete', 'gerät', 'geraet', 'analysegerät', 'analysegeraet',
'messgerät', 'messgeraet', 'analysator', 'controller', 'monitor',
];
private const ACCESSORY_FOCUS_KEYWORDS = [
'indikator', 'indikatoren', 'reagenz', 'reagenzien', 'zubehör',
'zubehor', 'ersatzteil', 'ersatzteile', 'verbrauchsmaterial',
'service set', 'serviceset', 'filter', 'pumpenkopf', 'motorblock',
];
private const ACCESSORY_FOCUS_VARIANT_MAP = [
'indikator' => ['indikator', 'indikatoren'],
'indikatoren' => ['indikator', 'indikatoren'],
'reagenz' => ['reagenz', 'reagenzien'],
'reagenzien' => ['reagenz', 'reagenzien'],
'ersatzteil' => ['ersatzteil', 'ersatzteile'],
'ersatzteile' => ['ersatzteil', 'ersatzteile'],
'service set' => ['service set', 'serviceset', 'service-set'],
'serviceset' => ['service set', 'serviceset', 'service-set'],
'service-set' => ['service set', 'serviceset', 'service-set'],
];
/**
* @param array<string, mixed> $config
*/
public function __construct(private array $config = [])
{
}
public function getTopProductLogLimit(): int
{
return 3;
return $this->int('top_product_log_limit', 3, 0);
}
/**
* @return string[]
*/
/** @return string[] */
public function getDeviceFocusKeywords(): array
{
return [
'geräte',
'geraete',
'gerät',
'geraet',
'analysegerät',
'analysegeraet',
'messgerät',
'messgeraet',
'analysator',
'controller',
'monitor',
];
return $this->stringList('device_focus_keywords', self::DEVICE_FOCUS_KEYWORDS);
}
/**
* @return string[]
*/
/** @return string[] */
public function getAccessoryFocusKeywords(): array
{
return [
'indikator',
'indikatoren',
'reagenz',
'reagenzien',
'zubehör',
'zubehor',
'ersatzteil',
'ersatzteile',
'verbrauchsmaterial',
'service set',
'serviceset',
'filter',
'pumpenkopf',
'motorblock',
];
return $this->stringList('accessory_focus_keywords', self::ACCESSORY_FOCUS_KEYWORDS);
}
/**
* @return array<string, string[]>
*/
/** @return array<string, string[]> */
public function getAccessoryFocusVariantMap(): array
{
return [
'indikator' => ['indikator', 'indikatoren'],
'indikatoren' => ['indikator', 'indikatoren'],
'reagenz' => ['reagenz', 'reagenzien'],
'reagenzien' => ['reagenz', 'reagenzien'],
'ersatzteil' => ['ersatzteil', 'ersatzteile'],
'ersatzteile' => ['ersatzteil', 'ersatzteile'],
'service set' => ['service set', 'serviceset', 'service-set'],
'serviceset' => ['service set', 'serviceset', 'service-set'],
'service-set' => ['service set', 'serviceset', 'service-set'],
];
return $this->stringListMap('accessory_focus_variant_map', self::ACCESSORY_FOCUS_VARIANT_MAP);
}
/**
* @return string[]
*/
/** @return string[] */
public function getDeviceQueryKeywords(): array
{
return self::DEVICE_QUERY_KEYWORDS;
return $this->stringList('device_query_keywords', self::DEVICE_QUERY_KEYWORDS);
}
/**
* @return string[]
*/
/** @return string[] */
public function getAccessoryQueryKeywords(): array
{
return self::ACCESSORY_QUERY_KEYWORDS;
return $this->stringList('accessory_query_keywords', self::ACCESSORY_QUERY_KEYWORDS);
}
/**
* @return string[]
*/
/** @return string[] */
public function getAccessoryProductKeywords(): array
{
return self::ACCESSORY_PRODUCT_KEYWORDS;
return $this->stringList('accessory_product_keywords', self::ACCESSORY_PRODUCT_KEYWORDS);
}
/**
* @return string[]
*/
/** @return string[] */
public function getDeviceProductKeywords(): array
{
return self::DEVICE_PRODUCT_KEYWORDS;
return $this->stringList('device_product_keywords', self::DEVICE_PRODUCT_KEYWORDS);
}
public function getExactProductNumberPhraseScore(): int
{
return 160;
return $this->int('scores.exact_product_number_phrase', 160);
}
public function getExactProductNamePhraseScore(): int
{
return 90;
return $this->int('scores.exact_product_name_phrase', 90);
}
public function getExactManufacturerMatchScore(): int
{
return 40;
return $this->int('scores.exact_manufacturer_match', 40);
}
public function getBrandContainedInNameScore(): int
{
return 20;
return $this->int('scores.brand_contained_in_name', 20);
}
public function getNameTokenOverlapWeight(): int
{
return 6;
return $this->int('scores.name_token_overlap_weight', 6);
}
public function getProductNumberTokenOverlapWeight(): int
{
return 10;
return $this->int('scores.product_number_token_overlap_weight', 10);
}
public function getCorpusTokenOverlapWeight(): int
{
return 2;
return $this->int('scores.corpus_token_overlap_weight', 2);
}
public function getNameNumberOverlapWeight(): int
{
return 18;
return $this->int('scores.name_number_overlap_weight', 18);
}
public function getProductNumberNumberOverlapWeight(): int
{
return 28;
return $this->int('scores.product_number_number_overlap_weight', 28);
}
public function getCorpusNumberOverlapWeight(): int
{
return 8;
return $this->int('scores.corpus_number_overlap_weight', 8);
}
public function getSizeMatchScore(): int
{
return 12;
return $this->int('scores.size_match', 12);
}
public function getAvailabilityBonusScore(): int
{
return 1;
return $this->int('scores.availability_bonus', 1);
}
public function getDeviceQueryDeviceProductBonus(): int
{
return 60;
return $this->int('scores.device_query_device_product_bonus', 60);
}
public function getDeviceQueryAccessoryPenalty(): int
{
return 120;
return $this->int('scores.device_query_accessory_penalty', 120);
}
public function getAccessoryQueryAccessoryProductBonus(): int
{
return 30;
return $this->int('scores.accessory_query_accessory_product_bonus', 30);
}
public function getAccessoryQueryDeviceProductBonus(): int
{
return 10;
return $this->int('scores.accessory_query_device_product_bonus', 10);
}
public function getContainsDigitPattern(): string
{
return '/\d/u';
return $this->string('patterns.contains_digit', '/\d/u');
}
public function getMatchingCleanupPattern(): string
{
return '/[^\p{L}\p{N}]+/u';
return $this->string('patterns.matching_cleanup', '/[^\p{L}\p{N}]+/u');
}
public function getWhitespaceCollapsePattern(): string
{
return '/\s+/u';
return $this->string('patterns.whitespace_collapse', '/\s+/u');
}
public function getTokenSplitPattern(): string
{
return '/[^\p{L}\p{N}]+/u';
return $this->string('patterns.token_split', '/[^\p{L}\p{N}]+/u');
}
public function wrapWithPaddingSpaces(string $value): string
{
return ' ' . trim($value) . ' ';
return $this->string('padding.prefix', ' ') . trim($value) . $this->string('padding.suffix', ' ');
}
/**
* @return string[]
*/
/** @return string[] */
public function getPriceNormalizationSearch(): array
{
return ['€', ' ', '.'];
return $this->stringList('price.normalization_search', ['€', ' ', '.']);
}
/**
* @return string[]
*/
/** @return string[] */
public function getPriceNormalizationReplace(): array
{
return ['', '', ''];
return $this->stringList('price.normalization_replace', ['', '', ''], true, ['', '', '']);
}
public function getPrimaryCustomFieldKey(): string
{
return 'migration_Backup_product_attr1';
return $this->string('custom_fields.primary', 'migration_Backup_product_attr1');
}
public function getSecondaryCustomFieldKey(): string
{
return 'migration_Backup_product_attr2';
return $this->string('custom_fields.secondary', 'migration_Backup_product_attr2');
}
public function getUseCasesCustomFieldKey(): string
{
return 'migration_Backup_product_attr4';
return $this->string('custom_fields.use_cases', 'migration_Backup_product_attr4');
}
public function getLanguagesCustomFieldKey(): string
{
return 'migration_Backup_product_attr5';
return $this->string('custom_fields.languages', 'migration_Backup_product_attr5');
}
public function getPrimarySecondarySeparator(): string
{
return ': ';
return $this->string('text.primary_secondary_separator', ': ');
}
public function getUseCasesLabel(): string
{
return 'Einsatzgebiete: ';
return $this->string('text.use_cases_label', 'Einsatzgebiete: ');
}
public function getLanguagesLabel(): string
{
return 'Sprachen: ';
return $this->string('text.languages_label', 'Sprachen: ');
}
public function getCustomFieldJoinSeparator(): string
{
return ' | ';
return $this->string('text.custom_field_join_separator', ' | ');
}
public function getDescriptionEmptyLinePattern(): string
{
return '/^[ \t]*\R/m';
return $this->string('description.empty_line_pattern', '/^[ \t]*\R/m');
}
public function getDescriptionWhitespaceCleanupPattern(): string
{
return '/[ \t]{2,}/';
return $this->string('description.whitespace_cleanup_pattern', '/[ \t]{2,}/');
}
public function getDescriptionMaxLength(): int
{
return 1500;
return $this->int('description.max_length', 1500, 0);
}
public function getPriceDecimals(): int
{
return 2;
return $this->int('price.decimals', 2, 0);
}
public function getPriceDecimalSeparator(): string
{
return ',';
return $this->string('price.decimal_separator', ',');
}
public function getPriceThousandsSeparator(): string
{
return '.';
return $this->string('price.thousands_separator', '.');
}
public function getPriceSuffix(): string
{
return ' €';
return $this->string('price.suffix', ' €');
}
public function buildRelativeSeoUrl(string $path): string
{
return '/' . ltrim($path, '/');
return $this->string('seo.relative_prefix', '/') . ltrim($path, '/');
}
public function getAvailableHighlightLabel(): string
{
return 'Verfügbar';
return $this->string('highlight.available_label', 'Verfügbar');
}
public function getUnavailableHighlightLabel(): string
{
return 'Nicht verfügbar';
return $this->string('highlight.unavailable_label', 'Nicht verfügbar');
}
public function getProductNumberHighlightPrefix(): string
{
return 'Produktnummer: ';
return $this->string('highlight.product_number_prefix', 'Produktnummer: ');
}
public function getMissingProductImagePlaceholder(): string
{
return 'no-image';
return $this->string('image.missing_placeholder', 'no-image');
}
public function getDeduplicationSeparator(): string
{
return '|';
return $this->string('deduplication.separator', '|');
}
}
private function int(string $path, int $default, int $min = PHP_INT_MIN): int
{
$value = $this->value($path, $default);
if (!is_numeric($value)) {
return $default;
}
return max($min, (int) $value);
}
private function string(string $path, string $default): string
{
$value = $this->value($path, $default);
if (!is_scalar($value)) {
return $default;
}
return (string) $value;
}
/**
* @param string[] $default
* @param string[]|null $emptySafeDefault
* @return string[]
*/
private function stringList(string $path, array $default, bool $allowEmptyStrings = false, ?array $emptySafeDefault = null): array
{
$value = $this->value($path, $default);
if (!is_array($value)) {
return $emptySafeDefault ?? $default;
}
$out = [];
foreach ($value as $item) {
if (!is_scalar($item)) {
continue;
}
$item = (string) $item;
if (!$allowEmptyStrings) {
$item = trim($item);
}
if (!$allowEmptyStrings && $item === '') {
continue;
}
if ($allowEmptyStrings || !in_array($item, $out, true)) {
$out[] = $item;
}
}
if ($out === [] && !$allowEmptyStrings) {
return $emptySafeDefault ?? $default;
}
return $out;
}
/**
* @param array<string, string[]> $default
* @return array<string, string[]>
*/
private function stringListMap(string $path, array $default): array
{
$value = $this->value($path, $default);
if (!is_array($value)) {
return $default;
}
$out = [];
foreach ($value as $key => $items) {
if (!is_string($key) || !is_array($items)) {
continue;
}
$cleanKey = trim($key);
if ($cleanKey === '') {
continue;
}
$cleanItems = [];
foreach ($items as $item) {
if (!is_scalar($item)) {
continue;
}
$item = trim((string) $item);
if ($item === '') {
continue;
}
if (!in_array($item, $cleanItems, true)) {
$cleanItems[] = $item;
}
}
if ($cleanItems !== []) {
$out[$cleanKey] = $cleanItems;
}
}
return $out !== [] ? $out : $default;
}
private function value(string $path, mixed $default): mixed
{
$current = $this->config;
foreach (explode('.', $path) as $segment) {
if (!is_array($current) || !array_key_exists($segment, $current)) {
return $default;
}
$current = $current[$segment];
}
return $current;
}
}