harden history find tokens

This commit is contained in:
team 1
2026-04-26 18:44:59 +02:00
parent e3fd4541e4
commit ff273ff9a0
8 changed files with 343 additions and 38 deletions

View File

@@ -212,20 +212,30 @@ final readonly class SearchRepairService
$topPrimaryProductNumber = $primaryShopResults[0]->productNumber ?? null;
$topPrimaryPhrase = trim($topPrimaryName . ' ' . ($topPrimaryProductNumber ?? ''));
$queries = [];
$queries = array_merge(
$queries,
$this->buildFocusedModelAccessoryQueries(
prompt: $prompt,
primaryQuery: $primaryQuery,
knowledgeText: $knowledgeText,
modelCandidates: $modelCandidates,
accessoryCandidates: $accessoryCandidates,
requestedAccessoryCodes: $requestedAccessoryCodes
)
$queries = $this->buildFocusedModelAccessoryQueries(
prompt: $prompt,
primaryQuery: $primaryQuery,
knowledgeText: $knowledgeText,
modelCandidates: $modelCandidates,
accessoryCandidates: $accessoryCandidates,
requestedAccessoryCodes: $requestedAccessoryCodes
);
if ($requestedAccessoryCodes !== [] && $this->config->shouldRestrictRequestedAccessoryCodeRepair()) {
foreach ($accessoryCandidates as $accessoryCandidate) {
if ($this->candidateMatchesRequestedAccessoryCodes($accessoryCandidate, $requestedAccessoryCodes)) {
$queries[] = $accessoryCandidate;
}
}
$queries = array_merge(
$queries,
$this->buildRequestedAccessoryFallbackQueries($requestedAccessoryCodes)
);
return $this->normalizeRepairQueries($queries, $primaryQuery);
}
if ($topPrimaryPhrase !== '' && $this->containsModelLikePhrase($topPrimaryPhrase)) {
$queries[] = $topPrimaryPhrase;
} elseif ($topPrimaryName !== '' && $this->containsModelLikePhrase($topPrimaryName)) {
@@ -246,6 +256,15 @@ final readonly class SearchRepairService
}
}
return $this->normalizeRepairQueries($queries, $primaryQuery);
}
/**
* @param string[] $queries
* @return string[]
*/
private function normalizeRepairQueries(array $queries, string $primaryQuery): array
{
$queries = array_map(
fn(string $query): string => $this->sanitizeQuery($query),
$queries
@@ -441,9 +460,7 @@ final readonly class SearchRepairService
$accessories = $accessoryCandidates;
if ($accessories === []) {
foreach ($requestedAccessoryCodes as $code) {
$accessories[] = 'Indikator ' . $code;
}
$accessories = $this->buildRequestedAccessoryFallbackQueries($requestedAccessoryCodes);
}
foreach ($models as $model) {
@@ -469,7 +486,7 @@ final readonly class SearchRepairService
{
$codes = [];
if (preg_match_all('/\b(?:indikator|indicator|reagenz|reagent)\s*([A-Za-z]{0,3}\s*\d{1,5}[A-Za-z0-9\-]*)\b/iu', $text, $matches) !== false) {
if (preg_match_all($this->config->getRequestedAccessoryCodePattern(), $text, $matches) !== false) {
foreach ($matches[1] ?? [] as $code) {
$normalized = $this->normalizeAccessoryCode((string) $code);
if ($normalized !== '') {
@@ -481,6 +498,51 @@ final readonly class SearchRepairService
return array_values($codes);
}
/**
* @param string[] $requestedCodes
* @return string[]
*/
private function buildRequestedAccessoryFallbackQueries(array $requestedCodes): array
{
$queries = [];
$templates = $this->config->getRequestedAccessoryCodeFallbackQueryTemplates();
$terms = $this->config->getRequestedAccessoryCodeFallbackTerms();
foreach ($requestedCodes as $code) {
$normalizedCode = $this->normalizeAccessoryCode($code);
if ($normalizedCode === '') {
continue;
}
foreach ($templates as $template) {
if (str_contains($template, '{term}')) {
foreach ($terms as $term) {
$queries[] = $this->applyRequestedAccessoryTemplate($template, $normalizedCode, $term);
}
continue;
}
$queries[] = $this->applyRequestedAccessoryTemplate($template, $normalizedCode, '');
}
}
return array_values(array_unique(array_filter(
array_map(fn(string $query): string => $this->sanitizeQuery($query), $queries),
static fn(string $query): bool => $query !== ''
)));
}
private function applyRequestedAccessoryTemplate(string $template, string $code, string $term): string
{
$query = str_replace(
['{code}', '{term}', '%code%', '%term%'],
[$code, $term, $code, $term],
$template
);
return $this->sanitizeQuery($query);
}
/**
* @param string[] $accessoryCandidates
* @param string[] $requestedCodes
@@ -505,7 +567,8 @@ final readonly class SearchRepairService
array $modelCandidates,
array $requestedCodes
): array {
$models = [];
$promptAnchoredModels = [];
$proximityModels = [];
$normalizedPrompt = $this->normalizeForRepairMatching($prompt);
foreach ($modelCandidates as $candidate) {
@@ -517,15 +580,24 @@ final readonly class SearchRepairService
$normalizedCandidate = $this->normalizeForRepairMatching($candidate);
$isPromptAnchored = $normalizedCandidate !== '' && str_contains($normalizedPrompt, $normalizedCandidate);
if ($isPromptAnchored) {
$promptAnchoredModels[$candidate] = $candidate;
continue;
}
foreach ($requestedCodes as $code) {
if ($isPromptAnchored || $this->modelAppearsNearAccessoryCode($knowledgeText, $candidate, $code)) {
$models[$candidate] = $candidate;
if ($this->modelAppearsNearAccessoryCode($knowledgeText, $candidate, $code)) {
$proximityModels[$candidate] = $candidate;
break;
}
}
}
return array_values($models);
if ($this->config->shouldPreferPromptAnchoredModelForRequestedAccessoryCode() && $promptAnchoredModels !== []) {
return array_values($promptAnchoredModels);
}
return array_values($promptAnchoredModels + $proximityModels);
}
private function candidateMatchesRequestedAccessoryCodes(string $candidate, array $requestedCodes): bool
@@ -564,17 +636,19 @@ final readonly class SearchRepairService
return false;
}
$codeNeedles = [
'indikator ' . $normalizedCode,
'indicator ' . $normalizedCode,
'indikatortyp ' . $normalizedCode,
$normalizedCode,
];
$codeNeedles = [$normalizedCode];
foreach ($this->config->getRequestedAccessoryCodeContextPrefixTerms() as $term) {
$normalizedTerm = $this->normalizeForRepairMatching($term);
if ($normalizedTerm !== '') {
$codeNeedles[] = trim($normalizedTerm . ' ' . $normalizedCode);
}
}
$codeNeedles = array_values(array_unique($codeNeedles));
foreach ($codeNeedles as $needle) {
foreach ($this->findNeedlePositions($normalizedText, $needle) as $codePos) {
foreach ($modelPositions as $modelPos) {
if (abs($codePos - $modelPos) <= 1600) {
if (abs($codePos - $modelPos) <= $this->config->getRequestedAccessoryCodeProximityWindow()) {
return true;
}
}
@@ -612,19 +686,18 @@ final readonly class SearchRepairService
return '';
}
$patterns = [
'/\b(Testomat(?:®)?\s+(?:\d{3,4}|EVO(?:\s+[A-ZÄÖÜ]{1,8})?|ECO(?:[-\s]?(?:PLUS|C))?|DUO(?:\s+\d{3,4})?|LAB(?:\s+[A-ZÄÖÜ]{1,8})?))\b/iu',
'/\b(Horiba\s+LAQUA\s+[A-Z0-9\-]+)\b/iu',
];
foreach ($patterns as $pattern) {
if (preg_match($pattern, $candidate, $matches) === 1) {
return $this->sanitizeQuery((string) ($matches[1] ?? ''));
$normalizedCandidate = $this->normalizeForRepairMatching($candidate);
foreach ($this->config->getModelCandidateExcludeTerms() as $term) {
$normalizedTerm = $this->normalizeForRepairMatching($term);
if ($normalizedTerm !== '' && preg_match('/\b' . preg_quote($normalizedTerm, '/') . '\b/u', $normalizedCandidate) === 1) {
return '';
}
}
if (preg_match('/\b(?:indikator|indicator|reagenz|reagent|verfuegbarkeit|verfügbarkeit|shop)\b/iu', $candidate) === 1) {
return '';
foreach ($this->config->getSpecificModelCandidatePatterns() as $pattern) {
if (preg_match($pattern, $candidate, $matches) === 1) {
return $this->sanitizeQuery((string) ($matches[1] ?? ''));
}
}
return $candidate;