p73
This commit is contained in:
@@ -0,0 +1,89 @@
|
|||||||
|
# RetrieX Patch p73 - Generic Accessory Shop Repair
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
|
||||||
|
Produktnahe Shop-Suchen mit Kombination aus Produktfamilie, Mess-/Anwendungsbegriff und Zubehörbegriff sollen nicht mehr in Geräte-only Ergebnisse abdriften, wenn der Shop den Mess-/Anwendungsbegriff nicht literal am Zubehörprodukt führt.
|
||||||
|
|
||||||
|
Beispiel:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Suche im Shop nach Testomat Resthärte Indikator
|
||||||
|
```
|
||||||
|
|
||||||
|
Vorher konnte die Suche trotz `Indikator` vor allem Testomat-Geräte liefern, weil `Resthärte` im Shop nicht als expliziter Produkttitel-/Metadatenbegriff am Indikator auftauchte und die Search-Repair-Logik ohne konkrete Typnummer keine Zubehör-Reparaturquery gebaut hat.
|
||||||
|
|
||||||
|
## Lösung
|
||||||
|
|
||||||
|
p73 ergänzt die bestehende Search-Repair-Logik generisch:
|
||||||
|
|
||||||
|
- Wenn eine Anfrage ein Zubehör-/Bundle-Signal enthält, aber keinen konkreten Zubehörcode wie `300` nennt,
|
||||||
|
- und keine spezifischen Zubehörkandidaten extrahiert werden konnten,
|
||||||
|
- wird aus der bereits bereinigten primären Shopquery eine fokussierte Zubehör-Reparaturquery gebildet.
|
||||||
|
|
||||||
|
Dabei werden generische Kandidatentokens aus der bestehenden YAML-Konfiguration entfernt, während Produktfamilien-/Markenanker und echte Zubehörbegriffe erhalten bleiben.
|
||||||
|
|
||||||
|
Beispielwirkung:
|
||||||
|
|
||||||
|
```text
|
||||||
|
testomat resthärte indikator -> testomat indikator
|
||||||
|
```
|
||||||
|
|
||||||
|
Dadurch kann die erweiterte Shopsuche passende Indikator-/Reagenzprodukte nachladen, statt die Antwort auf Geräte zu verengen.
|
||||||
|
|
||||||
|
## Generik / Governance
|
||||||
|
|
||||||
|
- Keine Testomat-Sonderlogik.
|
||||||
|
- Keine Resthärte-Sonderlogik im PHP-Core.
|
||||||
|
- Keine feste Indikator-300-Regel.
|
||||||
|
- Keine neue harte Tokenliste im PHP-Core.
|
||||||
|
- Zubehörbegriffe und generische Tokens kommen aus `genre.yaml` / vorhandenen Vocabulary-Views.
|
||||||
|
- Bestehende exakte Code-Precision aus p72 bleibt unberührt: explizite Codes wie `300` bleiben weiterhin präzise und ziehen nicht automatisch Varianten wie `300 S`.
|
||||||
|
|
||||||
|
## Geänderte Dateien
|
||||||
|
|
||||||
|
- `src/Commerce/SearchRepairService.php`
|
||||||
|
- `src/Config/SearchRepairConfig.php`
|
||||||
|
- `patch_history/RETRIEX_PATCH_73_GENERIC_ACCESSORY_SHOP_REPAIR_README.md`
|
||||||
|
|
||||||
|
## Lokale Checks
|
||||||
|
|
||||||
|
Ausgeführt im Patch-Arbeitsbaum:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php -l src/Commerce/SearchRepairService.php
|
||||||
|
php -l src/Config/SearchRepairConfig.php
|
||||||
|
php -l src/Agent/AgentRunner.php
|
||||||
|
php -l src/Config/AgentRunnerConfig.php
|
||||||
|
```
|
||||||
|
|
||||||
|
Zusätzlich wurde ein Reflection-Smoke-Test für die Repairquery-Bildung ausgeführt:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Suche im Shop nach Testomat Resthärte Indikator
|
||||||
|
primaryQuery: testomat resthärte indikator
|
||||||
|
=> repair query: testomat indikator
|
||||||
|
```
|
||||||
|
|
||||||
|
## Empfohlene Regressionstests in Nutzerumgebung
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bin/console mto:agent:config:validate
|
||||||
|
bin/console mto:agent:regression:test
|
||||||
|
bin/console mto:agent:config:audit-source --details
|
||||||
|
```
|
||||||
|
|
||||||
|
Fachliche Smoke-Prompts:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Suche im Shop nach Testomat Resthärte Indikator
|
||||||
|
```
|
||||||
|
|
||||||
|
Erwartung: Die erweiterte Suche darf Zubehör-/Indikatorprodukte nachladen und die Antwort darf nicht nur Geräte empfehlen.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Was ist der niedrigste Grenzwert für die Wasserhärte, welcher mit einem Testomaten überwacht werden kann?
|
||||||
|
mit welchem indikator wird der wert gemessen
|
||||||
|
was kostet der indikator
|
||||||
|
```
|
||||||
|
|
||||||
|
Erwartung: p72-Präzision bleibt erhalten; konkreter Code `300` zieht nicht automatisch `300 S`.
|
||||||
@@ -218,6 +218,18 @@ final readonly class SearchRepairService
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
$requestedAccessoryCodes === []
|
||||||
|
&& $accessoryCandidates === []
|
||||||
|
&& $this->asksForBundleOrAccessory($prompt . ' ' . $primaryQuery)
|
||||||
|
) {
|
||||||
|
$genericAccessoryQueries = $this->buildGenericAccessoryFocusRepairQueries($primaryQuery, $prompt);
|
||||||
|
|
||||||
|
if ($genericAccessoryQueries !== []) {
|
||||||
|
return $this->normalizeRepairQueries($genericAccessoryQueries, $primaryQuery);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$topPrimaryName = $primaryShopResults[0]->name ?? '';
|
$topPrimaryName = $primaryShopResults[0]->name ?? '';
|
||||||
$topPrimaryProductNumber = $primaryShopResults[0]->productNumber ?? null;
|
$topPrimaryProductNumber = $primaryShopResults[0]->productNumber ?? null;
|
||||||
$topPrimaryPhrase = trim($topPrimaryName . ' ' . ($topPrimaryProductNumber ?? ''));
|
$topPrimaryPhrase = trim($topPrimaryName . ' ' . ($topPrimaryProductNumber ?? ''));
|
||||||
@@ -269,6 +281,93 @@ final readonly class SearchRepairService
|
|||||||
return $this->normalizeRepairQueries($queries, $primaryQuery);
|
return $this->normalizeRepairQueries($queries, $primaryQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
private function buildGenericAccessoryFocusRepairQueries(string $primaryQuery, string $prompt): array
|
||||||
|
{
|
||||||
|
$source = trim($primaryQuery) !== '' ? $primaryQuery : $prompt;
|
||||||
|
$tokens = $this->tokenize($source);
|
||||||
|
|
||||||
|
if ($tokens === []) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$accessoryQueryTokens = $this->buildTokenSet($this->config->getGenericAccessoryFocusQueryTerms());
|
||||||
|
$accessorySignalTokens = $this->buildTokenSet(array_merge(
|
||||||
|
$this->config->getAccessoryOrBundleTerms(),
|
||||||
|
$this->config->getGenericAccessoryFocusQueryTerms()
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!$this->containsAnyToken($tokens, $accessorySignalTokens)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$removeTokens = $this->buildTokenSet($this->config->getGenericCandidateTokens());
|
||||||
|
foreach ($this->buildTokenSet($this->config->getAccessoryOrBundleTerms()) as $token => $_) {
|
||||||
|
if (!isset($accessoryQueryTokens[$token])) {
|
||||||
|
$removeTokens[$token] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$kept = [];
|
||||||
|
foreach ($tokens as $token) {
|
||||||
|
if (isset($removeTokens[$token]) || isset($kept[$token])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$kept[$token] = $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->containsAnyToken(array_values($kept), $accessoryQueryTokens)) {
|
||||||
|
foreach ($tokens as $token) {
|
||||||
|
if (isset($accessoryQueryTokens[$token])) {
|
||||||
|
$kept[$token] = $token;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->containsAnyToken(array_values($kept), $accessoryQueryTokens)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$maxTokens = max(2, $this->config->getCandidateWordCountCap());
|
||||||
|
$queryTokens = array_slice(array_values($kept), 0, $maxTokens);
|
||||||
|
$query = $this->sanitizeQuery(implode(' ', $queryTokens));
|
||||||
|
|
||||||
|
return $query !== '' ? [$query] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param string[] $terms */
|
||||||
|
private function buildTokenSet(array $terms): array
|
||||||
|
{
|
||||||
|
$tokens = [];
|
||||||
|
|
||||||
|
foreach ($terms as $term) {
|
||||||
|
foreach ($this->tokenize((string) $term) as $token) {
|
||||||
|
$tokens[$token] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string[] $tokens
|
||||||
|
* @param array<string, true> $tokenSet
|
||||||
|
*/
|
||||||
|
private function containsAnyToken(array $tokens, array $tokenSet): bool
|
||||||
|
{
|
||||||
|
foreach ($tokens as $token) {
|
||||||
|
if (isset($tokenSet[$token])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string[]
|
* @return string[]
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -293,6 +293,19 @@ final class SearchRepairConfig
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return string[] */
|
||||||
|
public function getGenericAccessoryFocusQueryTerms(): array
|
||||||
|
{
|
||||||
|
return array_values(array_unique(array_merge(
|
||||||
|
$this->getAccessoryCandidateTerms(),
|
||||||
|
$this->genreStringList('product_roles.accessory_product_terms.terms'),
|
||||||
|
$this->genreStringList('product_roles.requested_accessory_code_terms.terms'),
|
||||||
|
$this->genreStringList('product_roles.shop_views.accessory_query_terms'),
|
||||||
|
$this->genreStringList('product_roles.shop_views.accessory_product_terms'),
|
||||||
|
$this->genreStringList('product_roles.shop_views.accessory_focus_terms')
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
/** @return string[] */
|
/** @return string[] */
|
||||||
public function getSpecificityBoostTerms(): array
|
public function getSpecificityBoostTerms(): array
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user