feat(search): implement keyword→tag resolution in NlQueryParserService

Keywords that substring-match the tag taxonomy become OR-union tag filters;
non-matching keywords stay as FTS text. Resolved tags surface in the
NlQueryInterpretation as TagHint objects with effective colours. The
rawQuery fallback is now guarded by hadStructuredMatch to prevent
double-apply when all keywords resolve.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-06 22:54:33 +02:00
committed by marcel
parent 39ff63921d
commit dcd0e725a7
2 changed files with 93 additions and 7 deletions

View File

@@ -441,4 +441,41 @@ class NlQueryParserServiceTest {
assertThat(resp.interpretation().keywordsApplied()).isTrue();
}
// --- Tag resolution helpers ---
private Tag tag(UUID id, String name) {
return Tag.builder().id(id).name(name).build();
}
private Tag tag(UUID id, String name, String color) {
return Tag.builder().id(id).name(name).color(color).build();
}
private TagHint tagHint(UUID id, String name, String color) {
return new TagHint(id, name, color);
}
private static final UUID T1 = UUID.fromString("00000000-0000-0000-0001-000000000001");
// --- 24. Single keyword resolves to one tag → tag filter applied ---
@Test
void search_singleKeywordResolvesToTag_appliesTagFilter() {
Tag hochzeit = tag(T1, "Hochzeit");
when(ollamaClient.parse(anyString()))
.thenReturn(extraction(List.of(), "any", null, null, List.of("Hochzeit")));
when(tagService.findByNameContaining("Hochzeit")).thenReturn(List.of(hochzeit));
NlSearchResponse resp = service.search("Briefe über Hochzeit", PAGE);
ArgumentCaptor<SearchFilters> cap = ArgumentCaptor.forClass(SearchFilters.class);
verify(documentService).searchDocuments(cap.capture(), any(), any(), any());
assertThat(cap.getValue().tags()).containsExactly("Hochzeit");
assertThat(cap.getValue().tagOperator()).isEqualTo(TagOperator.OR);
assertThat(resp.interpretation().resolvedTags()).hasSize(1);
assertThat(resp.interpretation().resolvedTags().get(0).name()).isEqualTo("Hochzeit");
assertThat(resp.interpretation().tagsApplied()).isTrue();
assertThat(cap.getValue().text()).isNull();
}
}